SE-0366: Move Function + "Use After Move" Diagnostic

I’m generally in support of the proposal's scope. I too want to see __owned and __consuming formalized, but the roadmap indicates that’s in the offing, so I’m willing to wait for them. The same goes for move-only types and other such improvements. I want these things badly and I believe the authors when they tell me this is a good incremental step towards them.

I do feel like the move(_:) function is uncomfortable syntactically, but I don’t have a solid feeling about the right solution to that. So I’m going to ramble for a while and see where I end up.


Edit: You probably don’t actually need to read the rambly bit, so I’ve collapsed it

My first thought was that, since the argument to move(_:) is effectively modified (by ending its lifetime), it ought to be marked with &:

let y = move(&x)
consumeFinalY(move(&y))

This naïvely means that move(_:) should be <T>(inout T) -> T, not (__owned T) -> T, although that’s kind of an awkward fit since it leaves the argument uninitialized.

On the other hand, __owned actually has the same problem. This led me to think that perhaps the parameter to move(_:) should not be inout or __owned, but some new modifier—call it __moved as a strawman—that requires & and enforces the rules we want for arguments to move(_:). That would keep move(_:) from, say, being assigned into a variable for an <T>(__owned T) -> T function, and would perhaps allow other functions to use __moved to get the same behavior:

func consumeFinalY(_ y: __moved [Int]) { ... }
consumeFinalY(&y)

(This is basically the same as one of @ksluder’s suggestions above.)

But then I realized, do we actually want other functions using __moved? Or do we want functions to use __owned parameters and leave it to their callers to write move(_:), so they can choose whether they want moving or copying semantics on a per-call basis? On reflection, I think we want the latter—it’s more flexible and more explicit.


If so, I think I need to change my mental model of the situation: The move(_:) function itself is supposed to be the &-style marker of the unusual behavior being applied to the argument; the problem I’m having is that it looks so much like an ordinary function that my brain doesn’t flag it as marking unusual behavior.

I think that’s what @xwu is getting at when he suggests using a prefix-operator-style marker, like <-, to indicate the move. The problem I see with this is that, unlike &, <- is going to be very rare, so I’m worried that learning materials will skip over it and then people won’t know what it means when they see it in their code. Perhaps a different sigil would at least hint at the behavior more clearly (&&, perhaps?*), but something that involves a searchable word might be better.

* “You’re just copying && from C++!”, I hear you cry, but this is more a case of convergent evolution. In both C++ and this hypothetical Swift use, && basically indicates “kinda like &, but a little different and weirder”, but they would be used in very different places (call sites vs. parameter declarations).


That leads me to the keyword-style suggestions, like @move, #move(...), or just move.

I have to admit that I don’t like either of the sigiled forms. @ and # don’t suggest side effects to me; @ has never been applied to expressions and has always had an “adjective” feel that seems inappropriate here, while # generally has a "macro-like" connotation of being a shorthand for something you could have written out instead (although that’s not strictly true for #available). Neither one really feels appropriate for this.

That leaves us with plain move:

let y = move x
consumeFinalY(move y)

I like that contextual-keyword move is clearly not a normal function, but some kind of special language feature, much like try. It looks like something you haven’t seen before and should probably look up, and since it’s a word, you have a reasonable chance of looking it up successfully. But I am sensitive to the parsing concerns. I wonder if they could be addressed by also requiring an & on the operand:

let y = move &x
consumeFinalY(move &y)

The parser would then know that a move not followed by & is just the identifier move, so things like move(x) would not need to be explicitly banned.

But that does still leave the issue that it feels like overkill to design totally custom expression syntax for a niche feature. It doesn’t feel great to dedicate so much effort to something that won’t be used very often. (And realistically, to dedicate similar effort to similar things later in the roadmap, like the explicit copy operator we’ll need for @noImplicitCopy.) But maybe it’s the right thing to do anyway.


So I suppose where I end up is here: I think there are good ergonomic arguments for using a contextual keyword (perhaps with &), but I’m sympathetic to the notion that a contextual keyword would be too much effort for a feature so niche and we should just use a function (perhaps with &) and let the ergonomics suffer a little. I think the other ideas I’ve considered here fall into awkward spots where they’re too much effort, or too little ergonomic gain, or both.

9 Likes