With consuming one gets a var like argument + no implicit copy diagnostics, while with sendable one just gets a var like argument. From a codegen perspective they are actually the same. The only difference is the additional diagnostic.
The reason why it is separated is that conceptually one may not want sendable to imply no implicit copy. One can always achieve that by marking the parameter consuming sendable.
borrowing sendable is mainly expected to be used in situations where one needs to change an API to use sendable, but one cannot change from +0 -> +1 due to ABI constraints. An example of this would be the Continuation.yield(with:) API in AsyncStream. The API takes a result at +0 so if we changed the parameter to be just sendable, we would be breaking ABI.
It will actually be inout sendable. That is just a typo in the proposal.
The difference is that an inout parameter only provides guarantees around the actual memory usage of the var. It doesn't provide any guarantees around concurrency. Consider the following:
class MyKlass {
}
@MainActor func sendToMain<T>(_ t: T) async { ... }
func f(_ x: inout MyKlass) async {
await sendToMain(x)
}
func g() {
var x = MyKlass()
f(&x)
print(x)
}
There is nothing in the current semantics of inout that would prevent this code from being written and we would have a race in between print(x) and whatever occurs in sendToMain(x).
The interesting use case here can be seen in the mutex proposal where it is used: [Pitch] Synchronous Mutual Exclusion Lock.