SE-0467: MutableSpan

Hi everyone. The review of SE-0467: MutableSpan begins now and runs through March 25, 2025.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager via the forum messaging feature. When contacting the review manager directly, please keep the proposal link at the top of the message.

Trying it out

If you'd like to try this proposal out, you can download a toolchain supporting it:

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub.

Thank you for helping to improve Swift!

Joe Groff
Review Manager

16 Likes

Some test toolchains are here:
Ubuntu
Windows
macOS

In these toolchains the mutableSpan properties exist only for Array, ContiguousArray and ArraySlice.

2 Likes

The "Detailed design" section (and the implementation) contains:

extension MutableSpan: @unchecked Sendable where Element: Sendable {}

Should the conformance be:

  • unconstrained for noncopyable and nonescapable elements?

    extension MutableSpan: @unchecked Sendable
    where Element: Sendable & ~Copyable & ~Escapable {}
    
  • removed or unavailable — similar to SE-0331 for unsafe pointers?


The "Future directions" section contains an extracting method:

extension MutableSpan where Element: ~Copyable {
  public mutating func extracting(_ bounds: Range<Index>) -> Self
}

which seems identical to the method in the "Proposed solution" and "Detailed design".

1 Like

You're correct that the Sendable conformance should be unconstrained.

A Span<UnsafeRawPointer> won't be sendable due to UnsafeRawPointer not being Sendable, so there is no need to do more.

I did forget to remove the "extracting" subsection from "Future Directions." Will fix (fixed).

3 Likes

Interesting. In general the proposal makes sense and I don't have any objections.

Would it make sense to augment MutableCollection to automatically provide a mutableSpan property?

2 Likes

A general collection is not contiguous, and therefore cannot vend a MutableSpan property over its contents. It would be feasible to provide either an optional mutable span (a la withContiguousMutableStorageIfAvailable),¹ or a closure-taking operation, but it likely makes sense to delay designing that to go with a more systematic proposal rethinking collections to account for ~Copyable/~Escapable constraints.


¹ well, it would be possible once Optional supports ~Escapable wrapped types.

4 Likes

I'm still holding out hope that we formalize a ContiguousCollection protocol that guarantees this property--then it could provide span by default as well as mutableSpan when & MutableCollection.

(I'm going off on a tangent here, I know!)

1 Like

With MutableSpan, we're introducing a new reference-semantics struct to the standard library that represents a mutable variant of another immutable type. Unlike Unsafe(Raw)Pointer and UnsafeMutable(Raw)Pointer, however, there's (as written) no implicit conversion from MutableSpan to Span. Having to use .span instead is a fairly minor ergonomics issue, but since this is expected to be a currency type I think it's worth at least noting.

If the language supported it, I'd imagine MutableSpan would be a subtype of Span, with a declaration of something like:

@frozen
public struct MutableSpan<Element: ~Copyable & ~Escapable>: Span<Element>, ~Copyable, ~Escapable {

I've talked before on the forums about why I think subtype relationships between structs would be useful and natural, and this is another example of that. While that's not an entirely trivial feature to design, I'd hope that we wouldn't be blocked out of eventually supporting a true subtyping relationship for MutableSpan and Span (e.g. it'd be logical, albeit not essential, for mutableSpan as? Span<Type> to work) because of ABI concerns.

Aside: another example of a mutable-immutable struct subtype relationship

The reason I'm picking up on this particular point is because I've been toying with compiling Swift to SPIR-V shaders (based on the Embedded/no-allocation efforts, with a very basic example working), and realised there's the same Buffer<T>/MutableBuffer<T> (or StructuredBuffer/RWStructuredBuffer in HLSL naming) pattern there that would also benefit from subtyping. I don't think mutability is the only axis on which this is useful, though.

1 Like

Speaking personally and not as review manager, one concern I have with introducing an implicit conversion (in addition to the usual ones) is that MutableSpan and Span are quite different in their API surface area, and where they have commonality, the second-effort effects of their APIs under lifetime checking are quite different due to the exclusive vs. shared borrowing behavior of the types. If an implicit conversion from MutableSpan to Span led to a downstream series of shared-ownership operations being accepted by the compiler, but an unrelated change causes the conversion to stop occurring and those operations now require exclusivity, it could take a while to track down the indirect cause of the resulting errors.

4 Likes

Though it supports most of the API surface of Span, MutableSpan isn't a subtype in one important respect: Span: Copyable, while MutableSpan: ~Copyable — this is where MutableSpan is less capable than Span. Converting explicitly from using MutableSpan to Span is a useful step to (1.) indicate what prevents mutating accesses to the MutableSpan binding, (2.) have a binding on which to attach copyability.

4 Likes

This proposal has been accepted.

2 Likes