Just wanted to write this down: Joe, @Ben_Cohen, and I had a bit further discussion on this yesterday, which led to the idea that a property like reversed
really wants to be a sort of "forwarding" property: if you have a borrowed collection, you can get a borrowed reversed collection; if you have a mutable (inout
) collection, you can get a mutable reversed collection; if you have a collection you own, you can get a reversed collection you own. Or, in code:
func borrow<Element: !Copyable>(_ items: __shared [Element]) {
print(items.reversed.first) // Okay, we're just borrowing "x".
}
func mutate<Element: DefaultInit & !Copyable>(_ items: inout [Element]) {
let lastIndex = items.reversed.startIndex
items.reversed[lastIndex] = Element() // Not implemented today, but not totally unreasonable.
}
func consume<Element: Closeable & !Copyable>(_ items: __owned [Element) {
for __owned item : items.reversed {
item.close() // Destroys the element, so we must be destroying the entire Array.
}
}
As Joe noted above, you can do this kind of thing by yielding out of a property implementation instead of just returning a value, as long as the value you yield isn't copyable. This leads to the idea of properties potentially having three accessors to support move-only types: read
, which borrows self
; modify
, which treats self
as mutating
/ inout
; and __consuming get
, which can return an independent value but consumes self
in the process. And since these are independent entry points, the ones we don't use today can be added as additional protocol requirements for Collection in the future in a backwards-compatible way, as long as we're very careful about how the default implementation works / when it can get called. (The three of us worked out that either of Joe's bullet-point proposals above can support this.)
By the way, with copyable types, __consuming get
can be implemented as read
-then-copy, and conversely read
can be implemented as copy-then-get
. That's how the default implementations work. Move-only types are the case where having all three really matters.
P.S. Note that we can still have nonmutating modify
or nonmutating set
for things like UnsafeMutablePointer.pointee, and mutating get
or mutating read
for things like lazy properties on structs. There's no __consuming set
because setters can't return anything, and so you've consumed the value for nothing.
P.P.S. This isn't sufficient to solve the Slice problem on its own, by the way, because you want Slices to be mutated in-place when possible, but also for it to be okay to re-assign a Slice in a RangeReplaceableCollection like Array without touching the rest of the elements.