It does not. I've confirmed with Godbolt (above), benchmarks, and @_assemblyVision. And IMO, that's good; it's important to provide a way to opt-out of it.
My understanding is that exclusivity checking isn't really about reducing data races; it's important even for entirely synchronous code, and is closely related to alias analysis. If you have a function parameter of type inout T, the compiler needs to know that variable is not aliased anywhere else in the program, which allows it to avoid making temporaries.
If you have a COW data type, mutating operations must first create a unique copy of the buffer (header and elements), which eliminates most possibilities of aliasing. But you can still get in to trouble if, after making that unique copy, your implementation aliases the new buffer locally:
// - Ensures this function has the only reference to 'bufferCopy'.
let bufferCopy = buffer.ensureUnique()
// - This is a non-instantaneous mutating access to the new buffer's header.
bufferCopy.header.someMutatingFunction {
// - But we can perform an overlapping access if we reach-around and
// access the data via 'bufferCopy' again!
if bufferCopy.header.data == 0 { ... }
}
// But this should be fine. ManagedBuffer is a class so these properties
// live in different 'exclusivity domains' (term I made up).
bufferCopy.mutateElements {
if bufferCopy.header.data == 0 { ... }
}
I don't think I've ever seen a ManagedBuffer header with non-instantaneous mutating operations, but it can technically happen.
The current .header property catches this at runtime; but going through a pointer does not (which is a strong argument for not relaxing the current behaviour, although perhaps it could be a debug vs. release mode thing).
There isn't really a satisfactory way to get static checking of this, without special language support. Ideally, we'd be able to tag header-accessing/mutating operations as being in exclusivity zone "A", and element-accessing/mutating operations as being in zone "B", and operations on both as being in both zones. Then we'd be able to make a blanket statement that all COW types are safe from an exclusivity standpoint, and won't need dynamic enforcement.
Although perhaps something like that would be better as a true, native COW type (think something like an actor - a new kind of entity). It could guarantee you never forget to copy your data to a unique reference (reducing some of the boilerplate), ensure exclusivity, and generally just be a better ManagedBuffer. They would also be implicitly Sendable.
@Andrew_Trick, do you have any thoughts on the idea of having exclusivity "zones" or an entity for COW types?