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
MutableSpan
toSendable
ought to be unconstrained by the defaultCopyable
andEscapable
constraints: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
MutableCollection
protocol with a requirement to conditionally access a mutable span if the collection consists of contiguous storage, as a safe alternative to the existingwithContiguousStorageIfAvailable
requirement. With the acceptance of SE-0465: Standard Library Primitives for Nonescapable Types, we now have the foundational support inOptional
to 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
MutableSpan
toSpan
, by analogy to the existing implicit conversion for function arguments fromUnsafeMutablePointer
toUnsafePointer
.MutableSpan
cannot formally be a subtype ofSpan
, becauseSpan
isCopyable
butMutableSpan
is not. Fully general implicit conversions also have the potential to lead to surprising second-order behavior due to their different interactions with exclusivity. Since aSpan
only requires shared borrowed access to its underlying memory, butMutableSpan
requires 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
Span
s. 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 aSpan
orRawSpan
: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
MutableSpan
could 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 aSpan
but the caller ergonomics of the generic interface could be interesting, andMutableSpan
may 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 & Escapable
type to be loaded or stored as raw bytes from the referenced memory, and it is desirable for these types to beSendable
in order to facilitate divide-and-conquer operations over raw memory. However, since neither thestore
norunsafeLoad
operation require the type being stored or loaded to beSendable
, this could be used as a mechanism to pass non-Sendable
values across isolation boundaries.On the other hand, the
unsafeLoad
operation is unsafe: even setting aside concurrency isolation concerns, not everyBitwiseCopyable & Escapable
type 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
load
operation. We can further constrain that safeload
operation to only work with types that are alsoSendable
, either by havingSendable
be a prerequisite to being considered "fully inhabited", or havingSendable
be an independent constraint onload
itself.With that restriction in place, it should not be possible to use
MutableRawSpan
andRawSpan
together 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 alwaysSendable
and 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 & Escapable
types that are not alsoSendable
are theUnsafe*Pointer
family 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 requiringunsafeLoad
to work with those types is unlikely to be a burden.
Thank you to everyone who participated in the review!