No. __owned
is a property of the parameter, and you cannot imply it by something you write on the call site, just like you cannot make a function that takes an Int
suddenly accept a String
by writing something on the call site.
Sorry, this thread of discussion started with the question of whether __owned
already sufficed. So my follow-up question was in the context of the variant in which move
lives on the parameter declaration, which has since been suggested anew.
Forgetting about that variant, though: if move x
at the call site does not imply passing the argument as if the parameter were declared __owned
, does that mean move
doesnât avoid ARC traffic when calling resilient functions? Or does the guaranteed
ABI make it possible to move the argument into the parameter with zero ARC traffic?
usually you would not _ = move self
in the case block, you would do something like:
switch move self
{
...
}
for an array payload, it does not make a huge difference, but some payloads can be considerably more complex, and do not have a natural âemptyâ state.
A function that takes a borrowed (guaranteed) parameter is never going to be able to truly accept a move
of an argument value. The caller can "move" the value into a temporary and thus end its lifetime immediately after the call, but if the callee needs an owned value, it will need to copy the argument and thus incur e.g. RC overhead. The owned vs. borrowed distinction is important to get right for optimal performance; with move-only types, it's equally important to get right for basic correctness, and we'll probably force people to be explicit.
Ownedness is part of the ABI signature of the function, so you can make a resilient function with an owned parameter. But if you have an existing non-owned parameter, you're stuck with it; making it owned would be an ABI break.
This leaves me a little perplexed. This feature is being positioned as a performance tool, and using move
for arguments at the call site has been a major focus of discussion. But it sounds like the major performance benefit (minimizing copies and ARC traffic) silently doesnât happen if youâre calling a resilient function?
It has, but I donât think itâs addressed in the updated pitch. Does this pitch intend to include the option of move
decorating a function parameter in the definition, or is move
only for the caller?
Argument position = at the call site. Parameter position = at the declaration.
Though Iâll just go back and use âcall siteâ.
I hope my question is still addressed, despite being raised in confusion. Can a function parameter be defined to be move
like one can be defined inout
?
right now, _move(_:)
cannot be used in a computed property or subscript, so this still isnât possible to implement. iâm running into a similar issue, where i am trying to make the following âTreeâ view of an underlying Forest<Value>
buffer be mutable:
extension Forest
{
@frozen public
struct Tree
{
public
var forest:Forest<Value>
public
var first:Index
@inlinable public
init(forest:Forest<Value>, first:Index)
{
self.forest = forest
self.first = first
}
}
@inlinable public
subscript(head index:Index) -> Tree
{
_read
{
yield .init(forest: self, first: index)
}
_modify
{
var tree:Tree = .init(forest: _move(self), first: index)
yield &tree
self = tree.forest
}
}
}
Insert.swift:27:9: error: 'self' used after being moved
_modify
^
Insert.swift:29:43: note: move here
var tree:Tree = .init(forest: _move(self), first: index)
^
Insert.swift:30:13: note: use here
yield &tree
^
I tried to cover this in the second paragraph of my response, but I can elaborate.
Being able to explicitly cut short the lifetime of a local value avoids copies in two major ways:
- It allows the programmer to forward ownership of the value directly into something that naturally needs ownership, like an owned parameter or an assignment. This eliminates a copy of the value. This copy can also be potentially eliminated by the Swift optimizer, but being able to guarantee that it will happen is still valuable; for example:
- being explicit about things that are important is generally good,
- the optimizer will always be imperfect, and
- we are considering rules that will specifically tie the optimizer's hands in some cases (in exchange for more reliable semantics around destruction order and so on).
- Even if a direct copy is not avoided, it allows the programmer to reduce the number of outstanding copies at a given point in the program, which can avoid copies of buffers and other values due to the dynamic copy-on-write optimization.
Focusing in on one piece of that first point, there are two ways we can forward ownership value into a parameter during a call.
- The optimizer can analyze the callee to figure out that it wants ownership of the value, alter it to take an owned parameter, and then alter the call to forward the value. This is inherently a complex analysis that is therefore not going to happen reliably. It is also blocked by any sort of polymorphism in the call, including the "deployment polymorphism" of a resilient call, but also protocol dispatch, class dispatch, and all other kinds of indirect call. That is the only way in which resilience enters into this discussion.
- The function can just explicitly say that it wants ownership of the value. Today, the primary way to do this is with the unofficial
__owned
feature. This works through any kind of polymorphism, including resilience, as long as the call site knows about it. (For example, to make it work for protocol dispatch, both the concrete implementation and the protocol requirement must say that the parameter is owned.)
Thanks, that does clarify things considerably.
I completely agree with this.
However, I am concerned that being able to type foo(move x)
when the argument to foo
doesnât actually support moving is dangerous. It feels like inline
in C, which is notoriously unreliable. I would probably want the compiler to reject foo(move x)
unless the parameter was statically known to be __owned
(or consuming
). Which is isomorphic to my suggested alternative of making move
a parameter modifier instead of a call-site keyword.
edit I guess itâs not quite isomorphic. Swift already assumes __owned
and consuming
in some places, and itâs probably necessary to allow users to choose whether to move or copy into such arguments. So move
would become a superset of __owned
that mandates its argument be moved, my preferred syntax for which would be calling a function that returns a move T
return type.
It sounds like youâd like move
decorating the parameter at the call site to be analogous to the &
for calling inout
parameters, requiring the programmer to acknowledge they understand the ramifications of sending that parameter to that function. Is there anything like that now for the experimental features (__owned
, etc.)?
I wonder if it would help to align the naming of the __owned
parameter modifier and the move
operator name. I like take
as a name for both:
func consume(x: take Foo) {}
func makeAFooAndGiveItAway() {
let foo = Foo()
doStuffWithFoo(foo)
consume(take foo)
}
Another operator we've been considering is an explicit borrow operator, which would be a way to force passing an argument by borrow without copying it. Particularly when accessing class instance variables, globals, and other shared state, we will normally copy when they are passed as arguments to avoid imposing an exclusivity requirement on shared mutable state longer than we have to, but this is not always desirable:
class Foo {
var bar: Bar
}
func useBar(_: Bar) {}
func useFooBar(_ foo: Foo) {
// Will normally defensively retain/release foo.bar
useBar(foo.bar)
// Suppress the copy and pass the reference to foo.bar directly
useBar(borrow foo.bar)
}
Since that modifier aligns with the behavior of what we currently call __shared
parameters, borrow
also makes a nice name for explicitly labeling pass by borrow parameters:
func useBar(_: borrow Bar)
Then you can say that, in order to guarantee optimal no-copy behavior, you use the matching move
or borrow
modifier on the call site argument and the function definition.
It feels like the third line should be consume(give foo)
, since consume
is receiving the result of the expression.
Iâm very excited about borrowing. But how does borrow Bar
differ from inout Bar
? Is it a good idea to keep the familiar &foo.bar
syntax at the call site?
Iâm sorry to keep harping on this, but I would really like to hear some feedback from someone on the language workgroup about func move<T>(_: move T) -> move T
. I appreciate that the revised proposal directly responded to feedback about the pseudo-function syntax, but it feels like this new form of expression is actually going to wind up in many places, including places where very different syntax is already used for extremely similar effects.
Does anyone on the language workgroup have positive or negative opinions about going the other direction, making move(_:)
and friends real functions and reusing as much familiar syntax as possible?
Iâm very curious about this as well. I have no experience with __owned
or __shared
function parameters now. Can one call them now with undecorated variables the same way youâd pass to a âvanillaâ function parameter or must one decorate the passed variable with &
as one does for inout
parameters?
. sink
seems to also be used for the same purpose in val-lang. not sure if sink makes sense for swift but I do like the idea of using the same keyword from both parameter and using site.
A brief response to put the ByteBuffer[View]
digression to rest.
To be clear, this pair already exists. We have a ByteBufferView.init(_:)
that takes a ByteBuffer
and is isomorphic to .readableBytesView
, and we have a ByteBuffer.init(_:)
that takes a ByteBufferView
. Indeed, a goal of ours for some time has been to make it "zero cost" to jump between these two representations, as that allows us to produce a cheap way to operate on a ByteBuffer
when you need a collection (as ByteBuffer
isn't one).
In our case, then, what we want is explicitly to transform between these two cases. The initializers exist already so the flows back and forth are well-defined, they just happen to inevitably trigger CoW in the current Swift language.
That's ok, we have initializer pairs as discussed above, which forces a bit of cognitive burden on the developer, but I think a manageable amount.
More broadly, I think it's not clear exactly how move
should function on self
in a _modify
coroutine.
Fixing the mandatory COW on the initializers doesnât address the existing .readableBytesView
getter. How would you decorate that so the lifetime of the ByteBuffer
ends upon return from the getter?
Forcing the API designer to abandon getters for wrapping initializers would be an unfortunate regression in the languageâs expressive capabilities, especially because it would foreclose the ability to use opaque types for things like .readableBytesView
.
As I understand it, borrows would be immutable. So I would assume that we would not need the &
sigil for the same reason we don't need it when passing a variable to an immutable pointer parameter.
__owned
and __shared
don't change anything at the call site. In fact, you've already used them even if you haven't used the experimental keywords. Parameters to initializers (and newValue
for setters) are __owned
by default. All other function parameters default to __shared
.
To be clear, initializers are the preferred way of spelling conversion between types as per Swiftâs opinionated API guidelines. The point is well taken regarding opaque types. However, it bears mentioning that having a solution that avoids COW when spelled in the form of initializers is not a retreat from or abandonment of current practice but in fact reinforces the overall design direction of the language.