[Pitch] MutableSpan

Good point... But a third-party could retroactively make it conform. Of course, they shouldn't, but the problem being pointed out in the first place is that folks shouldn't unwisely make types conform to SIMDScalar, so this seems like a constraint out of expediency where the solution we'd still want anyway is to just fix SIMDScalar...

2 Likes

This has no parameter to attach a lifetime to. It would not compile, unless you annotate it to specify that you mean it to be immortal. An immortal lifetime means that the value is escapable.

No it hasn't. If S() is annotated as immortal, this will just return an immortal value. Otherwise it won't compile. Same for the computed property variant.

A computed property defines a non-mutating get accessor by default, unless you explicitly define another kind of accessor. This is just that, a get accessor. Where is the ambiguity?

1 Like

S is ~Escapable. Yet it clearly escapes by being returned. That’s the only way it’s useful. @John_McCall confirms that this even extends to yielded owned values, which are somehow different from borrowed values???

Where is the single document I can read to understand this system?

Thanks for the prompt! Right over here.

4 Likes

Thanks for the reminder. I clearly need to review that document. From a quick scan, I can’t tell whether the rules presented in this pitch are anticipated by that document or if they fill a hole that document intentionally left.

I do need to update that document with (1) the names we're currently thinking about and (2) a better handling of yielding get, because I dismissed that case as useless when I initially wrote it, then gradually realized it was actually useful.

Here's the quick summary of the interaction between yielding, value ownership, and lifetime dependencies that I didn't have time to write earlier.

yielding functions produce a single value for their callers to use, but have the ability to do something after the caller is done with it. This is really only useful when the value has setup and teardown; moreover, the teardown means they probably don't want to let the value continue to be used later. So whatever the function yields, it's gotta be scoped to the duration of the yield: the caller must promise to stop using it before it ends the yield by resuming the yielding function.

This scoping also means you can yield things that you can't normally return, like a mutable (inout) reference to a variable. You can also yield an immutable reference to a value, which we call a borrow. (Borrows are useful with non-copyable types, but also as well as a way to optimize performance by avoiding copies.) Regardless of the type of the variable/value, yielding either of these is always scoped, because they represent ongoing memory accesses that have to end at some point. But it can also make sense to yield a lifetime-dependent value that the caller will completely own, as long as that value doesn't escape the scope of the yield in the caller; this can only be enforced with a ~Escapable type.

That is the general picture. There is an important special case where you can return a reference to a variable/value that's the caller is already guaranteeing to make safe; this happens all the time with value semantics. In this case, you don't necessarily need a yielding function, and the lifetime of the mutable reference / immutable reference / lifetime-dependent value might be tied to some wider scope in the calling function (or even one of its callers).

7 Likes

There’s a subtlety here which I don’t think has yet been reflected in the {Mutable,}Span pitches: the difference between yielding an ~Escapable value and returning an ~Escapable value. My read of this pitch, and of the Span proposal that preceded it, is that any returned ~Escapable value carries a lifetime dependency on the reference/access/borrow that it came from. (Nailing down this definition is another source of confusion.) But this seems too much; ~Escapable values can be useful on their own (e.g. to implement RAII), and it should be possible to write a factory function that returns an ~Escapable value with its own independent lifetime. All of the rules about Copyable & ~Escapable seem like additional special cases that try to plug this hole.

Introducing an explicit syntactic difference between yielding an ~Escapable value and returning one makes much more sense. Does that mean we can do away with the special rules involving Copyable?

2 Likes

There is now a prototype of MutableSpan to try out:
It is target "Span" on the "span-prototype" branch of the swift-collections repository:

MutableSpan requires a recent Swift toolchain, at tag swift-DEVELOPMENT-SNAPSHOT-2025-02-17-a or later. Note that running the tests on macOS with a custom toolchain works in Xcode, but not from a command line.

The prototype explores 4 possible approaches to slicing MutableSpan: extracting() functions returning Self, an extracting subscript returning Self, a range subscript returning a new slice type, and forgoing Collection-style slicing by including range parameters to bulk operations.

2 Likes