@frogcjn Yes, that will work. But I’m really trying to avoid needing the associated type to be Sendable, as that is a severe constraint for my situation.
@x-sheep This is a good question! In general, no. An async function requirement is pretty flexible and you can still use isolated functions in a conformance. However, this was along the right line of thinking.
The protocol I had was designed in a way that is just fundamentally hard to use without modification. To be callable, it has to be safe to move a type of Value into the use. Making it Sendable is the easy route. But there are other ways.
protocol AsyncRequirement {
associatedtype Value
nonisolated func use(_ value: sending Value) async
}
actor UsesValue: AsyncRequirement {
// error: Non-Sendable parameter type 'Self.Value' cannot...
func use(_ value: sending Int) async {
}
}
Today, the compiler also rejects this. And I think that really drives home the point that the build-time validation happening here is not considering the sendability of the arguments at all.
Here’s what I actually settled on:
protocol AsyncRequirement {
associatedtype Value
nonisolated(nonsending) func use(_ value: Value) async
}
actor UsesValue: AsyncRequirement {
nonisolated func use(_ value: Int) async {
await internalUse(value)
}
private func internalUse(_ value: Int) {
// ...
}
}
This code is acceptable because the stage of validation happening believes (correctly) that the value cannot be transferred across isolation boundaries. But, then internally a transfer is exactly what I do.
I then got thinking even more high-level about this. Not only is my particular implementation safe, I think even more general implementations could also be safe.
class NonSendable {}
protocol AsyncRequirement<Value> {
associatedtype Value
nonisolated func use(_ value: sending Value) async
}
actor UsesValue: AsyncRequirement {
// error: Non-Sendable parameter type 'Self.Value' cannot be sent...
func use(_ value: NonSendable) async {
}
}
func test() async {
let ns = NonSendable()
let a: any AsyncRequirement<NonSendable> = UsesValue()
// transferred via RBI
await a.use(ns)
}
I think it is possible that this particular compiler error is not just inappropriate for Sendable types, but for all types. Because it could be the case that a value is successfully sent, and it would only ever be knowable at a particular callsite.
But my understanding of protocols is limited and it could be there are situations I’m not thinking of.
(I’m also not 100% sure that my contrived example here correctly demonstrates what I’m trying to say. But I think it at least gets the general idea across.)