Understanding by-value captures in @Sendable functions

apologies if you know this already, but a capture of a mutable local variable like this essentially places the value in a reference-counted 'box' that lives on the heap. this is why changes to the value are 'visible' across different execution contexts (this is nicely explained in this comment/thread).

as such, the issue here is less about 'race conditions' and consistency (though that is also something worth considering), and more about avoiding 'data races' (ah, and it seems you've authored a nice post on the distinction between those terms). the diagnostic prevents capturing these 'by-reference' captures in @Sendable closures because such closures may execute concurrently, and mutations of the capture in such cases could cause data races.

the implementation for this concurrency diagnostic is quite conservative – it is currently based entirely on information present when typechecking, and doesn't use any kind of 'dataflow analysis', like that which powers region based isolation (though commentary suggests some more sophisticated analysis may be an eventual capability). and as a slight aside – one reason i think the diagnostics have to be (perhaps surprisingly) conservative in cases like this is that there's no way to 'tell' the compiler that a closure will be run at most once. so the compiler essentially has to assume a @Sendable closure could be invoked multiple times, concurrently, and apply rules that ensure such cases are still safe.

in your specific example, assuming there are never any writes to the captured value, then i think the code should be data-race free (perhaps by definition), but it will require some source modifications[1] currently to convince the compiler that is the case.


  1. a let or capture list item for immutable values, and Mutex or explicit unsafe opt-outs for mutable cases. â†Šī¸Ž

5 Likes