slice.dropFirst(1) returns ArraySlice<Uint8>, yet the buffer remains unchanged after mutation. How come that payload does not point to the same underlying storage?
That applies to all methods in the iterator family, i.e prefix, suffix, drop, etc.
It's also unclear when copying occurs and how dropFirst(1) is different from slice[slice.index(after: slice.startIndex)...].
It looks like once you define a variable with var, the whole thing is being copied either immediately or upon mutation, haven't verified. Does it happen the same way when using let variable?
How does one make a reference to avoid going through the same variable, especially inconvenient when offsets are involved? Drop to raw pointers and YOLO? I don't see any way to reference a mutable subrange.
This isn't quite true, but I can see how it feels that way.
Because you made a copy.
This is not entierly obvious, but once you bring payload into existence there are now two copies of the array: one in the variable called buffer (and aliased as slice within func test), one in the variable called payload. When you subsequently write into the variable in payload, you trigger the copy-on-write pattern, which copies buffer into payload and then mutates it.
You can fix this by continuing to operate directly on slice:
The copy occurs at the point where you call withUnsafeMutableBytes, as this is your first mutating access to the variable payload. dropFirst(1) is exactly identical to slice[slice.index(after: slice.startIndex)...], and you could replace it in your code without changing the behaviour.
let variables cannot be mutated through, and so will not trigger the copy-on-write.
(Of course, that may not the most efficient way to do it as you may see intermediate copies; you're probably better off using slice directly, but this is the semantic reason why the code doesn't have the result you expect)
Oh, that's a good point; I forgot about the dropFirst() changing the length.
Slices are values, not references. Since they are values, they are independent collections.
The concept of a "view" does not really exist in the language, but we use that term to refer to values which might share their underlying storage with another variable (rather than being a completely fresh Array or whatever). They cannot have shared mutable state, so mutations cannot happen in-place if the storage is shared.
Admittedly, this can be a little counter-intuitive if you are expecting them to be references or behave like classes in other languages. You should think of them more like independent values.
Just like other value types, if they have a uniquely-referenced storage, the mutation can happen in-place. But I would suggest that that is more of an implementation detail and not necessary to understand the high-level semantics.
To add to that, the place where the original Array gets mutated is when the slicing subscript setter is called; the place where that happens is after the function call ends and the inout is…concluded, for lack of a better term. Everything up to that is mutating the independent value typed ArraySlice (which itself is copied).
I don’t think it’s possible to build a safe “view” on a value-typed collection without move-only types, but perhaps we’ll be able to explore that in the future. Meanwhile, if you want to mutate the original Array, you either have to inout all the way down (which, admittedly, the current Collection APIs do not make easy; you want &slice[offset: 1...]), or pass the base collection and the indexes separately.