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.
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
.
Yes. Span
and MutableSpan
have reference semantics. However Swift models exclusivity through mutations of the value, not of the referent.
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 mutating
ness:
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.
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.
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