[Pitch] Move Function + "Use After Move" Diagnostic

That is somehow deeply painful to think of as Swift code.

Honestly, I’ve been waffling back and forth on whether to advocate for making this a keyword. I’m currently leaning towards keywords again, especially if it isn’t going to be put in a namespace. The risk of mistaking it for a normal function is too high.

Swift has been commonly criticized for having too many keywords, but I’m not entirely sure it’s a problem. If something doesn’t play by normal rules, having it stick out is a good thing.

Many people mistake that for boilerplate, but when I read Swift code I never see anything extraneous: every part always conveys information to either the compiler or the reader.


FYI, I posted a pitch for consuming/nonconsuming here: [Pitch] Formally defining consuming and nonconsuming argument type modifiers.


I've come around to supporting the functionality of this after reading Joe Groff's excellent roadmap, but am not a fan of the wording. move to me suggests something else, or a number of something elses. It's not really what's going on here. Here the object is being deleted from memory, so it can't be accessed afterwards. Whether something else retains a copy is secondary.

And using _ = move() is confusing. _ = XX. suggests XX is being evaluated, i.e. it's being moved, but then the result thrown away. But that's not really what's happening. And the English language can be used in a way already used elsewhere in Swift to avoid this.


// to dispose of x call release
// but if a copy is needed call released
y = released(x)

This mirrors the use of e.g. sort and sorted for Array. released is just a nice way of writing out both the copy and release, i.e. is identical to

    y = x

Doing it this way puts the release first as I think it should be. "release" has other meanings in computing but in code normally means something like this. Other possibilities include free & freed or dispose & disposed. I actually thought of expire and expired first but that's probably too odd.


i think the main reason for introducing move() is to move binding, for e.g.:

let y = move(x)

move() can be used for ending lifetime of binding, for e.g.:

let _ = move(x)

but i doubt "to end lifetime of binding" is the main reason for introducing move() function.


But that is what's happening. For _ = move(x) the value is being moved out of x then thrown away. The release is a side effect.


I keep thinking about the name of this and the fact that is just a function that can't be implemented in the language.

In my mind it would be perfect if the language could implement it int he same way that is implemented in Rust.

And after reading the roadmap it feels like it can get close but not completely....

So the purist in me feels that it shouldn't be a function. But then again, we see to have other functions in the language that are not implementable at user space so maybe a more pragmatic viewpoint is needed.

I think the problem is deeper: It's hardly a normal function at all...
Implementing the function-part is trivial (it returns the value you put in, so it is just identity), but the side effect is extremely strange (when comparing with ordinary Swift):
It's not only able to somehow change a value declared with let, it even "destroys" its argument, so that it can't be used anymore at all.
Given the fact that Swift is quite restrictive when it comes to redeclaring something without creating a new scope, this is a massive change (for what a function can do).


Moved out of and thrown away, yes, not moved. move on its own suggests something quite different. It could be called moveOutOfAndThrowAway but that's not very Swifty. But there are single English words which better describe what's happening, such as those I suggested.

It seems to me that one part of the syntax-level dilemma is that there's two ways of looking at move():

It destroys a binding, yielding the value previously referred to by that binding, which is not a concept expressible in Swift, and therefore feels "keyword-y"


It sets the value referred to by a binding to be uninitialized and returns the old value, which is mostly a concept that already makes sense and even mostly already exists (since you can have a variable declared with no initial value, and it'll error if used), and therefore feels a bit more "function-y".

Like you can almost imagine implementing move() like this:

func move<T>(_ x: inout T) -> T {
  defer { 
    x = uninitialized //doesn't actually exist
  return x

I'm almost tempted to suggest = uninitialized as a spelling for this in fact, except that

x = uninitialized

is clunkier than

foo(move x)

(and might have ordering issues in more complex uses, since it's not an expression)


Indeed. And if we want to go even further, the next stage of "variable lifetime" could be "undeclared again" to return situation back to the state where you write "var x: T" (potentially with another type) again. In the scoped version this is equivalent to exiting the inner scope.

let x: A
foo(x) // error: uninitialized
x = expression
foo(x) // ok
uninitialize x // pseudo syntax
foo(x) // error: uninitialized
let x: B // error: already defined
unlet x // pseudo syntax
let x: B // ok

Sorry to ask this again: are there cases that can be elegantly expressed by move/drop machinery but can't be expressed by do {} scopes (or are too cumbersome in the latter form)? In other words is move a mere syntax sugar? Would like to see some killer example (with, say, 5 lines with new move machinery and 30 equivalent lines with old do {} scopes method).

let moving:[Int] = [1, 2, 3]

let moved:[Int] = move(moving)

If you’ve ever written any code that uses locks, you’ve probably encountered situations where cleanup happens on several paths, but at different times or in different orders. That’s a nightmare to do solely with scopes. Rust has shown how similar locking and memory safety actually are, so I suspect there would be similar caes for move().


Apologies if this idea has been discussed and dismissed elsewhere but I agree with the sentiment that move is a very unusual function compared to any other, which makes me think that it should be a keyword and not a function. Why should move be a function and consuming be a keyword? I know the latter kind of has to be but this seems inconsistent to me. I understand the motivation to make this a function to work with a cute usage like “_ = move(x)”, but do we really want our code to contain lines that look like that? A beginner would likely have absolutely no idea what that code would do, even if consumeX(move x) is fairly innocuous.

In addition, as an expert feature it seems too easily accessible IMHO, which makes me think it’d be too easy to get it wrong. Easy things should be easy, expert things should have some kind of warning sign attached, which IMHO a keyword would provide.

One other thing comes to mind, how does this interact with inner scopes: (edit: the detailed design suggests that the following would not compile after all - to be honest I’d understand why but it does mess with my understanding of the feature and / or scoping somewhat)

func foo() {
    let x = MyType()
    do {
        _ = move(x)
    print(x) // possible?
1 Like

Given the example of moving inside an if statement from the proposal, that print should be invalid as x is uninitialized.

1 Like

Just a thought: given that a function that takes an argument provided by move is consuming, was there consideration given to calling the function (or keyword..) consume?

_ = consume(x) would be much clearer to a beginner than _ = move(x) IMHO.

Just a quick update. I was able to implement the defer handling that was asked for up thread. If you want to try it out, check out the toolchains attached below.

On another note, I looked into the keyword solution that people were talking about in the thread above. Turns out changing move and (and referencing the roadmap thread) copy to be keywords is likely to cause source breaks. All methods with the name move, copy would have to be modified to use backpacks "`". In contrast the function approach will keep all such methods working (that is no source break) and one would still be able to use move/copy, but would need to refer to the full name of the function: Swift.move, Swift.copy.


macOS: https://ci.swift.org/job/swift-PR-toolchain-osx/1272//artifact/branch-main/swift-PR-40518-1272-osx.tar.gz
Linux: https://ci.swift.org/job/swift-PR-toolchain-Linux/772//artifact/branch-main/swift-PR-40518-772-ubuntu16.04.tar.gz

The PR: [DRAFT] Just for toolchain by gottesmm · Pull Request #40518 · apple/swift · GitHub


Can we not get away with a contextual keyword here? Since move will always be applied to a binding (lexically, an identifier) it seems like we should be able to allow, say, func move(x: Int) { ... }, move(0), etc., and only have move operate as a keyword when it is directly juxtaposed with another identifier, a la move x.

Would that still be source breaking?



enum IntBuilder
    func buildExpression(_ value:Int) -> Int
    func buildBlock(_ values:Int...) -> [Int]
func build(@IntBuilder _:() -> [Int])
    let move = 0
1 Like

Nice :slight_smile:

Of course, I’d expect the breakage here to be significantly reduced (perhaps negligible?) compared to completely disallowing all functions and properties named move/copy. If we were introducing move in a new language version, then we could prefer the current interpretation in Swift 5 mode and require the above to be written as


in Swift 6 mode.

1 Like

If we are going to use a name other than move for swift I think we need an extremely strong case to do so. Move is the term of art in every other language for this behavior (that I know of) along with move only types. I fear this is going to be another case where more people look at swift as that language with the weird names for everything.

I would be good with any of these syntax styles personally:

move foo // if move becomes a keyword