I'm a little concerned that we don't have all of the bases covered with respect to functions lifted into nominal types. Consider the following:
func identity<T: ~Copyable>(_ t: consuming T) -> T { t }
public struct FuncNC<A: ~Copyable, B: ~Copyable>: ~Copyable {
let run: (borrowing A) -> B
init(run: @escaping (borrowing A) -> B) { self.run = run }
borrowing func callAsFunction(_ a: borrowing A) -> B { run(a) }
consuming func map<C: ~Copyable>(_ f: @escaping (borrowing B) -> C) -> FuncNC<A, C> {
.init { a in f(self(a)) }
}
consuming func flatMap<C: ~Copyable>(_ f: @escaping (borrowing B) -> FuncNC<A, C>) -> FuncNC<A, C> {
.init { a in f(self(a))(a) }
}
consuming func join<C: ~Copyable>() -> FuncNC<A, C> where B == FuncNC<A, C> {
self.flatMap(identity)
}
}
as of the 6.0-DEVELOPMENT-SNAPSHOT-2024-03-26-a build, join on this type produces:
<unknown>:0: error: copy of noncopyable typed value. This is a compiler bug. Please file a bug with a small example of the bug
Is there something fundamentally wrong with join here?
There also seems to be problems with type inference and consuming. Consider:
public struct ContinuationNC<T: ~Copyable, R: ~Copyable>: ~Copyable {
public let run: (@escaping (consuming T) -> R) -> R
public init(_ run: @escaping (@escaping (consuming T) -> R) -> R) {
self.run = run
}
func callAsFunction(_ callback: @escaping (consuming T) -> R) -> R {
run(callback)
}
consuming func map<U>(
_ transform: @escaping (consuming T) -> U
) -> ContinuationNC<U, R> {
.init { downstream in self {
t in downstream(transform(t))
} }
}
}
In the map function this produces:
't' is borrowed and cannot be consumed
Shouldn't it infer that t is consuming?
And finally, I've raised this before, it is not possible, but it would be correct behavior on the ContinuationNC type to be able to mark callAsFunction as being consuming. That would be a great future direction to see in this pitch.
It should raise an error. identity is a (consuming T) -> T function declaration, but flatMap takes a (borrowing B) -> FuncNC<A, C> parameter, and we can't convert from a function taking a consuming parameter to one that takes a borrowing parameter without a thunk that copies the parameter, which is probably tripping that internal diagnostic. What happens if you change flatMap's parameter's parameter to be consuming as well?
Yeah, that looks like a bug as well.
There shouldn't be any reason callAsFunction can't be consuming. These all sound like bugs to me not directly related to noncopyable generics.
I agree that much of the above is bug-related more than design-related, but the general difficulty of the topic has forced me to wonder if what I think are bugs really are bugs or if they are gaps in my understanding.
I think that of the examples above what I'm concerned most about for review purposes is the bit about variadics. Its not clear to me if the intent of the proposal is to allow protocol suppression to be specified in most places where you could allow protocol conformance to be specified. Or maybe not.
Variadics stress my understanding of the proposal. Should variadic suppression of copyability be allowed or not under this proposal?
More abstractly, I will say that, like others, I'm finding the syntax associated with specifying "make no assumptions about copyability" vs "instances of this type cannot be copied" to be very difficult to explain to colleagues. But I think this is restating what others have already said.