Hi everyone. The review for SE-0467: MutableSpan concluded on March 25, 2025, and the Language Steering Group has decided to accept the proposal with some minor modifications.
Reviewers raised a few questions about the proposal:
-
The conformance of
MutableSpantoSendableought to be unconstrained by the defaultCopyableandEscapableconstraints:extension MutableSpan: @unchecked Sendable where Element: Sendable & ~Copyable & ~Escapable {}The authors acknowledge that this was an oversight in the proposal text, and the proposal should be updated to suppress those implicit constraints.
-
It might be interesting to extend the
MutableCollectionprotocol with a requirement to conditionally access a mutable span if the collection consists of contiguous storage, as a safe alternative to the existingwithContiguousStorageIfAvailablerequirement. With the acceptance of SE-0465: Standard Library Primitives for Nonescapable Types, we now have the foundational support inOptionalto express this requirement:protocol MutableCollection { ... var mutableSpan: MutableSpan<Element>? }We leave this design direction for future proposals to explore.
-
Reviewers raised the potential of introducing a subtyping or implicit conversion relationship from
MutableSpantoSpan, by analogy to the existing implicit conversion for function arguments fromUnsafeMutablePointertoUnsafePointer.MutableSpancannot formally be a subtype ofSpan, becauseSpanisCopyablebutMutableSpanis not. Fully general implicit conversions also have the potential to lead to surprising second-order behavior due to their different interactions with exclusivity. Since aSpanonly requires shared borrowed access to its underlying memory, butMutableSpanrequires fully exclusive access, source changes that lead to changes in where implicit conversions occur could lead to nonobvious ownership knock-on errors.That said, the LSG acknowledges that there is likely to be a related broader issue with APIs that want to work with
Spans. The most natural way to write a function that wants to read in a contiguous block of memory, agnostic to how the memory is owned, is to take aSpanorRawSpan:func process(elements: Span<Int>)However, this passes burden onto callers who do own the memory to project out the
Span:var info: Array<Int> process(elements: data.span) var mutableInfo: MutableSpan<Int> process(elements: mutableInfo.span)and
MutableSpancould be seen as one instance of this more general problem. It's tempting to introduce a protocol with avar span: Span { get }requirement, and have people write generic functions over that protocol, but the generic interface will add overhead compared to only taking aSpan, because of either protocol dispatch or unnecessary generic specialization. A mechanism to allow for the ABI of taking aSpanbut the caller ergonomics of the generic interface could be interesting, andMutableSpanmay be one instance of that broader problem. We are OK with leaving these alternatives to be explored as future directions. -
There was discussion as to whether it is safe for
MutableRawSpan(andRawSpan) to conform toSendable. These types allows for values of anyBitwiseCopyable & Escapabletype to be loaded or stored as raw bytes from the referenced memory, and it is desirable for these types to beSendablein order to facilitate divide-and-conquer operations over raw memory. However, since neither thestorenorunsafeLoadoperation require the type being stored or loaded to beSendable, this could be used as a mechanism to pass non-Sendablevalues across isolation boundaries.On the other hand, the
unsafeLoadoperation is unsafe: even setting aside concurrency isolation concerns, not everyBitwiseCopyable & Escapabletype can be loaded from an arbitrary bit pattern.SE-0447 left it as a future direction to introduce another layout constraint for "fully inhabited" types, which can be safely formed from arbitrary bit patterns in memory. This constraint could then in turn be used as the constraint for a safe
loadoperation. We can further constrain that safeloadoperation to only work with types that are alsoSendable, either by havingSendablebe a prerequisite to being considered "fully inhabited", or havingSendablebe an independent constraint onloaditself.With that restriction in place, it should not be possible to use
MutableRawSpanandRawSpantogether to break isolation boundaries with safe code, and thus it should ultimately be safe to have both types conform toSendable. The LSG feels that this is the right tradeoff to make, since we foresee binary encoding and decoding to be an important use case for these types, and that task will typically use byte and numeric types which are alwaysSendableand fully inhabited. Saddling these use cases with the need for unsafe escape hatches in order to distribute work among tasks would be burdensome and introduce the potential for unintentional unsafety in the common case.The most prominent case of
BitwiseCopyable & Escapabletypes that are not alsoSendableare theUnsafe*Pointerfamily of types, which would never be safe to load from an arbitrary bit pattern, and although we don't want to preemptively rule out the possibility, it is likely to be rare for a fully inhabited safe-to-load type not to beSendable, and so requiringunsafeLoadto work with those types is unlikely to be a burden.
Thank you to everyone who participated in the review!