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

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().

4 Likes

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.


Toolchains:

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

3 Likes

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?

4 Likes

yes.

@resultBuilder 
enum IntBuilder
{
    static 
    func buildExpression(_ value:Int) -> Int
    {
        value
    }
    static 
    func buildBlock(_ values:Int...) -> [Int]
    {
        values
    }
}
func build(@IntBuilder _:() -> [Int])
{
}
build 
{
    let move = 0
    let 
    x
    = 
    1
    
    move 
    x
}
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

move;
x

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)
move(&foo)
move foo // if move becomes a keyword

What weird names?

Personally I’ve disliked “move” as a descriptor ever since I saw it in C++, mainly because it’s confusing.

When we move(x), where does x move? Nowhere, I think. (What actually happens would depend on the type I guess.)

If it was called contentsOf(x), you’d have a better idea of what happening.

1 Like
let x = Something()
let y = move(x)

My understanding is that it's called move because the value has moved out of x and into y. It is no longer in x.

1 Like

contentsOf is meaningless as the name of function that moves value of binding

Sure, but if you come across this in code for the first time, that not what it says.

See, one problem here is that I don’t think about programming in terms of what compilers do, my model is based on my knowledge of machine language and moving bytes around.

So ‘x’ refers to a memory location, and that memory location either contains a number we use directly, or a number we use to find a different memory location which contains whatever.

‘x’ is an entry in a table mapping the symbol to that first memory location.

So does move(x) change the mapping table, the memory location, or something else?

We would not be banning functions/properties named move/copy. One would go through normal name resolution rules. So it would be like if one defined any function in ones own module that has the same name as a function in the stdlib: one will still call the local function. In such a case if one wishes to call the stdlib version one can disambiguate by explicitly referring to move as Swift.move.

Right, I understand how the function approach avoids any source breakage. I was talking about as compared to the (non-contextual) keyword approach, for which you said:

maybe. keep in mind still, that move as a keyword does not generalize well, if we want to have more functions with similar semantics in the future

If the same name is used by both the proposed global function and other methods (in the standard library or elsewhere) which API should have the @warn_unqualified_access attribute?

For example:

Sure, but IMO that generalization is only desirable insofar as the move function actually behaves like a function. At it stands, there are, in my view, important differences between move(_:) and other standard library functions, even the other standard library functions that are somehow 'special'.

I take @David_Smith's point that move feels ~function-y~ in some sense, but even with the example pseudo-implementation for move:

there would not be a way to express the fact that move operates only on local bindings to storage, rather than arbitrary mutable values (also, inout would prevent its use on let declarations).

Another thing the ownership manifesto mentions is that move would be unable to be applied indirectly, which I assume means that something like:

let f: (Int) -> Int = move

would be illegal? Or would the above work, but permit the following?

func doSomething(with x: Int, fn: (Int) -> Int) {
  fn(x)
  print(x) // has 'x' been moved?
}

doSomething(0, move)

I get the desire to avoid introducing keywords to the language willy-nilly, but if we have to introduce so many special cases just to call this a function, IMO we have a distinction in name only. Users would not be able to understand move just by reading the signature or implementation, so I expect it would inevitably have to be taught as "don't think of this as a normal function, think of it like this..."

Anyway I really want to make sure we don't dismiss the keyword option over minor syntactic issues that we can find ways to work around.

ETA: Also, if generalizability for the sake of future ownership features is part of the motivation for making move a function, the proposal should explicitly call that out. The only other such feature I'm aware of is copy, and although it hasn't been proposed, off the top of my head I can't think of why copy couldn't just be a function—AFAICT it obeys normal Swift function semantics.

9 Likes

When teaching iOS at the University, working with co-workers, contractors, or interviewing with Google. They have all found swift syntax and naming weird. ¯_(ツ)_/¯
For me I am totally used to the naming conventions and they make sense. It seems like it is one of those things some devs have to get used to.

The examples I remember most off the top of my head are the Unsafe[Mutable][Raw][Buffer]Pointer types and associated global functions. Naming of CodingKey/CodingKeys. Some other just odd things that don’t quite live up to the rest of stuff.