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

Why? Have any other languages used this syntax for move? Whereas several languages have used it for assignment.

let b <- a

would be more natural here. but then how best to drop?

_ <- a
nil <- a
unlet a
drop(a)
unbind(a)
remove(a)
_ = move(from: a)
...

Because I want move is more simply to use than copy, but making move default on COW type will be source breaking

Pitching and taking consuming (née __owned) through evolution before the move function would probably clear up a lot of the confusion here.

6 Likes

When Swift can declare move-only values (on the caller side, or as part of the type), that's exactly how the move or drop function will work, without any magic, just like the Rust examples. The compiler Builtin inside move's implementation will be superfluous in that case. The nice thing about the move that we're proposing is that it has the same semantics for any argument value. That does require magic to distinguish normal pass-by-value semantics from move semantics.

There are alternatives to using compiler magic to handle copyable argument values. I'm not a syntax afficionado, and I don't see any precedent in the language for something like this, but we should at least consider them:

  1. A move keyword that works as an operator

foo(move arg)

  1. A move parameter modifier

A free-standing move function could be written as

func move<T>(_ t: move T) -> T {
  return t
}

Or even expressed as a prefix operator.

Any user-defined function could force argument moves.

This runs counter to the current Swift practice in which parameter modifiers only affect semantics on the callee-side. Normally, without any recognizable syntax on the caller-side, argument semantics are unaffected. For example __owned does not currently force a move (nor should it).

7 Likes

consume is good, but it will be overloaded with other "consuming" uses, which consume their argument value, but do not prevent implicit copies and subsequent uses of the variable. move consumes its value and also ends the variable lifetime.

3 Likes

Just because something can be spelled a certain way doesn’t mean it should be spelled that way. For example, functions can implement the exact same semantics of computed properties. But Swift chooses to give computed properties the same syntax as stored properties because it helps the programmer’s understanding.

I would argue that modeling move as a function harms the programmer’s understanding. The classical definition of a function is a map from domain to range. In this light, move(_:) is the identity function. It’s only when you expand the definition of function to include its effects on the calling environment that you can even start to explain the semantics of move(_:).

As you illustrate, there’s a good chance that a keyword will be required at the call site to indicate that the client is unbinding the value. Why add the extra layer of function call syntax atop that? Let the keyword do the heavy lifting.

7 Likes

Would it be possible to somehow reference the target binding, rather than using the assignment operator? Assignment isn’t what’s happening, after all. I think that’s the crux of the issue here.

struct SortedArray {
    var values: [String]
    
    init(values: [String]) {
        // Ensure that, if `values` is uniquely referenced, it remains so,
        // by moving it into `self`
        move(\values, to: \self.values) // Syntax of local key path expressions subject to change
        // Ensure the values are actually sorted
        self.values.sort()
    }
}

That would still require a tweak to the grammar to allow local key path expression literals (which need not resolve to a public KeyPath type for the moment), but I think it’d fit what is actually happening much better: you are operating on bindings, not values.

For that to work, you’d also need some way to make new declarations with let or var, ideally while preserving type inference, so obviously the idea needs some work.

1 Like

When you move something you often want to move it to a function argument, like this:

let something = Something()
prepare(with: move(something)) // avoid making a copy here

A move function taking a source and destination variable cannot express this.

4 Likes

…Huh. You’re right, short of some even-more-confusing currying nonsense, that’d be unworkable.

I was also going to suggest a replacement for the assignment operator, but that wouldn’t work for function calls either.

That being said, if people are going to be nesting this function in other calls, that makes it even more important that it doesn’t look like a normal function.

3 Likes

I found a proposed syntax for move operations in Swift, dating back around a decade ago.

It proposes two operators: an infix <- for moving a binding to another, and a prefix <- for use in function calls.

I think this is actually a better approach than using a pseudo-function. Using a prefix for function calls like that also has strong precedent: inout parameters and pointer conversions use &.

5 Likes

Not bad.

Another alternative for prefix:

foo(^x)
let y = pop(x)
let y = contentsOf(x)
let y = innardsOf(x)
let y = visceraOf(x)

let y = *x

?

I don’t think there is anything wrong with the functionality of move(). Very useful looking tool.

I’d like to second that both the syntax and name of move(thingyYouWannaMakeUnique) is a little confusing to a person unfamiliar with the SIL. I also believe “move” as a name is a very valuable one for many domains, such as game programming, simulations, etc. Perhaps using a less core english verb or moveSIL etc would be a better choice for the Swift ecosystem, or placing the functionality in a non-function call looking syntax.

I don’t have a better suggestion for either, and perhaps that’d be a fine name and syntax given the high quality intro new features like this can be given in WWDC doc. I don’t see that fixing the collision with the highly useful name of “move” for other domains though.

1 Like

I want to re-emphasize this point. move(_:) is very likely to be the signature of a method. With implicit self, how would code inside a sibling method spell the move operation? Builtin.move(_:)?

1 Like

Swift.move(_:) This is no different from a method shadowing any other standard library function.

3 Likes

I remain deeply opposed to making it top-level. It should be inside a caseless enumeration, like the MemoryLayout functions.

2 Likes

Swift.move(_:) This is no different from a method shadowing any other standard library function.

Yes, and this strikes me as problematic.

1 Like

Top-level functions of any kind should be treated as exceptional.

Functions with no type constraints should also be considered exceptional.

Top-level functions with no type constraints in the Standard Library with a word that has myriad domain-specific meanings should be almost unheard of.

This is contradicted by probably the first function that a user learns about in the standard library: print. There are languages where the analogous facility is namespaced, but Swift is not designed that way. Let’s avoid articulating unsubstantiated “should be’s” that are plainly contrary to what the language already is.

4 Likes