What does the `Result: ~Copyable` constraint mean in the Mutex API (and in general)?

currently, the Mutex type's locking method has the following signature:

public borrowing func withLock<Result: ~Copyable, E: Error>(
  _ body: (inout sending Value) throws(E) -> sending Result
) throws(E) -> sending Result

i'd like to focus on the Result: ~Copyable generic constraint, as i don't quite understand its motivation, nor the current apparent behavior. my interpretation is that this constraint implies that the value returned from the body closure must be a ~Copyable type. would this then preclude the ability to do something like make a copy of the mutex-protected data and return it from the withLock closure? e.g.

let protectedState = Mutex(1)
let doubled = protectedState.withLock { 2*$0 } // `Result` type is `Int`, which does not conform to `~Copyable`

assuming this assessment is accurate – what is the motivation for the ~Copyable constraint here?

secondly, is the ~Copyable constraint on the Result type in the prior example actually being enforced by the compiler today? it appears to compile without issue, and allows Copyable types to be returned from the closure. this is somewhat surprising since analogous formulations that require a particular conformance on their generic parameters do not behave analogously. e.g.

func returnSomeNonCopyable<Result: ~Copyable>(
  _ body: () -> Result
) -> Result {
  body()
}

protocol P {}

func returnSomeP<Result: P>(
  _ body: () -> Result
) -> Result {
  body()
}

let works = returnSomeNonCopyable { 42 }
let fails = returnSomeP { 42 } // 🛑 Global function 'returnSomeP' requires that 'Int' conform to 'P'

Result: ~Copyable in a generic constraint means "Result is not required to be copyable" (i.e. the constraint imposes no requirement on its copyability), not "Result must be noncopyable".

See SE-0427 for the gory details, and feel free to ask follow-up questions.

5 Likes

thank you! that pretty much resolves my confusion. one follow-up question i have which was not immediately obvious to me – is there a way to construct a generic function that does require a noncopyable parameter, vs one that simply admits them? i have no use case in mind, just wondering about what is or is not possible to represent in this domain.

1 Like

There isn't. The reasoning is that a copyable thing can do everything a non-copyable one can, simply by refraining from copying it. It's vaguely unsatisfying :joy: (to me at least), but it makes sense.

4 Likes

No (at least, not one that can be enforced at the type level); the generic system does not admit "negative constraints" (i.e. the ability to require the absence of a protocol conformance). @Slava_Pestov can probably expand on why exactly that is.

1 Like