SE-0456: Add Span-providing Properties to Standard Library Types

Unlike Atomic, though, it is meaningful to be able to mutate a Span or MutableSpan independent of the memory being referenced, just like you can mutate an Unsafe[Mutable]BufferPointer to advance the range of memory it references independent of mutating that referenced memory.

4 Likes

So really we’re talking about ā€œreference semanticsā€ vs. ā€œvalue semanticsā€. Just like Atomic is actually a type with reference semantics—the ā€œvalueā€ of an Atomic is its memory address, and you have to call load() to access the value of its, erm, Value.

2 Likes

Yes. Span and MutableSpan have reference semantics. However Swift models exclusivity through mutations of the value, not of the referent.

1 Like

IIUC (I know it is an active research area), the core principle of value semantics is that a modification of one variable does not affect the 'semantic value' of any other variables. This is responsible for things like the improved local reasoning of value types.

If you make your type non-copyable, you can guarantee that mutable state is not shared, which is another way to get that isolation between variables -- in other words, you use uniqueness to get value semantics.

One of the things that I think is very promising about these new language features is that I think they can allow us to build a Span that is closer to Array than the Unsafe*Pointer types.

I'd really love if it if for Span, its 'semantic value' was its contents (like Array), not its memory address, so replacing an element of a mutable span would be considered a modification of that span. I notice that it does have isIdentical and range(in otherSpan: Span) -> Range<Int>? operations for address-level operations, but for most other usage I'd rather not think of it as a pointer.

Jumping back to a specific question from earlier:

From the point of view of the code using it, it would be mostly the same. However, the access to the value vending it would still be exclusive (one cannot get a MutableSpan without mutating access to the value vending it).

Note that there wouldn't be a good way to fashion multiple read accesses by borrowing a new MutableSpan from the original one, because every subsequent borrow would invalidate the previous borrow context in order to preserve exclusive access.

This is besides the point, however, because the decision between var and let resides in client code! A library would not be able to vend both read-only access and read-write access through a MutableSpan property, because it would require overloading two get accessors over mutation. This is no more possible than overloading two functions over their mutatingness:

extension Array {
    func myF() {}
    mutating func myF() {}
  |                   `- error: invalid redeclaration of 'myF()'
}

A library would presumably be able to vend read-only access through a closure-taking function that takes a borrowing MutableSpan parameter, but we are trying to reduce the number of closure-taking functions in use.

In general, a MutableSpan-typed let-binding is not very useful, but it is harmless.
A nonmutable MutableSpan is less flexible than a Span proper.

On the other hand, a Span stored in a var binding is useful, as it can be (for example) a way track of the amount of information that remains to be decoded in a buffer. In that situation the content isn't modified, but the bounds get shrunk as processing progresses.

2 Likes

In contrast, I suppose, to @lorentey's guesses at how often these will be called by "regular Swift code", I think they will actually become very common. For example, we should aim to add Span-taking API to Foundation for things like Data init (copying), JSON/PropertyListDecoding (borrowing), file writing (borrowing), networking, and probably more.

Therefore I prefer the straightforward and simple names bytes for RawBytes and storage or contents for the typed one.

I'm neutral on the name for the typed version, but I am sympathetic to the argument that contents may already be in use on the kinds of types that we need to add this to.

However - this is not a protocol. Types with API that predates the name decided here are free to choose another one if they want for the typed Span. It's not the end of the world if they do. If a caller uses the wrong one the compiler will simply tell them it's the wrong type for the function they are passing it to - and it can probably offer a fixit most of the time.

7 Likes

Hello Swift Community,

Thank you all for your participation in the pitch and review. The proposal has been accepted with modifications.

Doug Gregor
Review Manager

3 Likes