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

So it still exists, but gets marked as illegal? Why?

Essentially you are saying that in this example the scope of x ends right after move(x) is the subsequent let x defining x in a new scope. Interesting. I think both view points are valid and there is no clear winner here.

2 Likes

For the record I asked about reassignment not because I wanted it but because I was trying to refine my mental model of what was going on. I think reassignment after move is a foot gun that we should avoid.

But the bigger point I wanted to make is that “move” as a function name/keyword doesn’t really capture the fact that the variable name is going to be inactivated for the remainder of the scope.

I like “drop” that someone suggested above. But whatever term we choose I would love if it somehow captured the “shutting down the use of the variable name” effect more than “what the compiler is doing”.

1 Like

Mmm, I think there’s actually a clear winner where, which is the solution that doesn’t make let equivalent to var:

From a compiler standpoint, these would be two different lets.

I fully understand the mental model you’re talking about. What I’m saying is that this can be achieved with the pitched design as-is using var, whereas the alternative you mention has the effect of making local let bindings no different from var bindings without any gain in expressiveness. That’s not a desirable outcome, since the fact that you can’t rebind let is pretty essential to its existence in distinction to var.

2 Likes

yep, totally different:
let x = 42
drop x
let x = "Hello World"

A new declaration would not work very well after some control flow that might or might not have moved the value:

let x = 42

if Bool.random() {
   _ = move(x)
}

// Error: something may or may not be bound to `x` at this point
let x = 8*6

I think it's more reasonable reassigning to a var:

var x = 42

if Bool.random() {
   _ = move(x)
}

// simple assignent after `x` was possibly moved
x = 8*6

Will the compiler allow assigning to a maybe-moved variable?

I was thinking it would use the same principle as the inverse: if a binding might or might not have been moved, the binding can’t be used and another binding can’t use that identifier.

1 Like

I’m inclined to agree that it would be confusing to little benefit. That being said, I suspect that the name of this function, “move”, is inspired by languages where this is how it works (particularly C++). As a result, the name incorrectly implies a binding no longer exists at the previous identifier.

If this is actually marking a binding as unusable, it should be given a name that reflects that better.

The more I think about @Saklad5 idea the more I like it... it's about NOT creating a special case of "half alive" variables that do not exist but whose name is still somehow reserved...

yep. except for this case of course, which you nailed here:

As for the naming: alternative name for drop could be unbind.

Except in Swift it could actually still end up doing that, due to branching control flow.

I’m not necessarily recommending my interpretation be used. However, I do think the function’s semantics should be adjusted to make it clear that isn’t what is happening.

I believe it would be worth considering.

Imagine a more relaxed language:

var x = 1
// use x as int here
var x = "Hello"
// use x as string here

It's totally obvious what's going on here, x is used, then unbound, then used as another variable. And in swift we'd just have an explicit drop / unbind in between:

var x = 1
// use x as int here
unbind x / drop x / or similar
var x = "Hello"
// use x as string here

if this is not allowed, how would I do this? introduce some {} braces?

I am strongly opposed to allowing a binding to be overwritten by being redeclared.

As noted in the original pitch, the standard way to confine declaration scope in Swift is creating and immediately calling a closure. I am quite fond of that approach, actually, and sorely miss it when using other languages.

1 Like

But does this mean that the binding still acts as retaining the memory? In other words, would mutating ‘y’ trigger a copy if the value was a COW type?

I don't see how my example:

var x = 1
// use x as int here
unbind x / drop x / or similar
var x = "Hello"
// use x as string here

is any different to what you were suggesting earlier:

To me that's actually the opposite: I miss the approach used in C++ where you can define a scope unrelated to closure, because sometimes you need just the scope. Declaring and immediately calling a closure just for the purposes of another scope sounds like a hack.

ps. another hacks that define a new scope would be if true { ... } and 'repeat { ... } while false`. I hate those less as at least those do not involve creating a temporary thrown away closure.

It's totally obvious what's going on here, x is used, then unbound, then used as another variable.

Those two examples are equivalent, but you’re implying a continuity between the first x and the second x. I’m not.

Anyway, I think we’ve diverged from my main point: the function being proposed has behavior that does not align with its name or semantics.

We shrink the lifetime after emitting all of the diagnostics.

1 Like

Can you just use a ‘do { }’ to make scopes?

6 Likes

what do you mean by that?! The two xs are two completely different variables... belonging to two different scopes...