import Synchronization
final class BoxValue {
let value = 0
}
final class Model: Sendable {
var value: BoxValue {
get {
_value.withLock(\.self)
}
set {
_value.withLock {
$0 = newValue
}
|- error: 'inout sending' parameter '$0' cannot be task-isolated at end of function
`- note: task-isolated '$0' risks causing races in between task-isolated uses and caller uses since caller assumes value is not actor isolated
}
}
private let _value = Mutex(BoxValue())
}
I really don't understand the error. For me, it smells like a bug.
Pretty sure it's correct, it just doesn't clearly tell you why. Since BoxValue isn't Sendable, and you pass a new instance in with value.set, technically it's possible you kept another reference to the value passed in and can use it unsafely, despite the passed reference in the Mutex.
Jon's assessment seems correct to me â the Mutex API is designed to try and enforce that you cannot 'sneak' something into it that isn't Sendable and could potentially still be referenced elsewhere, thus introducing potential data races despite protecting mutable state with a lock.
to deal with this constraint, the language offers the sending parameter attribute[1], which indicates that a value is known to be in a 'disconnected' isolation region, so can't be aliased in a different one. currently, however, i don't think there's any way to indicate that property accessors deal in sending values, so i'm not sure the style of convenience accessor you've outlined here can be made to work in this manner (perhaps something like it could be done with macros).
the natural thing to try next to achieve the goal of fully replacing the non-sendable value would be to perform the assignment with a value that is known to be 'disconnected' in this way. i.e. something like:
unfortunately, this formulation currently also fails to compile in the Swift 6 mode, which seems like a bug[2]. the only workarounds i'm aware of involve boxing the sending value within a closure somehow and performing the assignment via that, e.g.