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

A late review; I've been taking a bit of an extended holiday and am still catching up on things.

+1 to having this operation in the language in some form.

I like having this as a keyword, I think. It's a fundamental operation. I would ask that it be extended to support multiple parameters:

var x = ...
var y = ...

move x, y

The result should be a tuple of all the values. Since it is a keyword rather than a function, I assume this would not require variadic generics (?).

It's also fine to add that later, but it's the kind of small quality-of-life thing that I doubt anybody's going to bother to write a separate proposal for.

I'd rather we accept multiple parameters (if we agree that it's a thing we want), and leave that part of the implementation until later if needed.

Naming Things

Whilst I acknowledge the ARC predictable performance thread, I wish there was a fresher, fuller manifesto about how we want to introduce ownership at the language level. Because, as I understand it, that's what the operations in that thread really amount to, and I think we would benefit from a more complete picture which allows us to use a consistent terminology.

I see two styles which I think would work - either one which evokes the idea of lifetimes, or one based on ownership.

To explain, I'll start with "copy". It's a highly overloaded term - even in Swift, we use the term "copy-on-write", which talks about a whole different kind of copying than the copy we're talking about avoiding here, or what NSCopying/NSMutableCopying mean by a copy.

I feel the ownership manifesto could be clearer about what it means, but I was wondering about it recently and figured a more obvious definition might be:

'copy' takes a value as input, and returns the same value but with an unconstrained lifetime. The caller has control over when it wants to end the lifetime of the returned value.

Or, as @Joe_Groff expressed in code:

func copy<T>(_ x: borrow T) -> T {
  return x
}

It's quite an amazing operation - to arbitrarily extend the lifetime of any value; to turn a borrow in to a non-borrow. We've just acquired an ownership stake over something we didn't own before, because we now have a coequal say over its lifetime.

Does this amount to a "copy"? For reference types, extending the lifetime/acquiring that ownership stake amounts to calling retain, and we have it for as long as we like - until we call the balancing release. I'm not sure that everybody would understand that operation as being a "copy", and I think it could use a less ambiguous name.

If we're going for lifetime terminology, extendLifetime seems fitting for this operation. If we're going for ownership, perhaps acquireOwnership? Given that this function is an opt-in for people who want this stuff to be explicit, I don't think an explicit name is too bad. Better than an ambiguous name.

Instead of @noImplicitCopy, I quite like @explicitLifetime/@explicitOwnership.

That brings us to move-only types - and I find it awkward because the interesting thing about a move-only type is not that it can be moved, but that it disallows copies. You kind of need to look at the negative space to understand the name and why it's an interesting concept.

If by "copy" we mean "obtain a version of the value which we own/whose lifetime we control", then what we have been calling a "move-only" type is describing data which Swift cannot share or duplicate the ownership of. It is uniquely owned - such that there can only be one variable which is known to control the lifetime of any "instance" of a struct, which is a concept arises once we have this new concept of uniqueness (but because it has unique ownership, we don't need reference counting for it).

And since we disallow sharing/duplicating ownership, the only other way you have to pass their contents around is by move (i.e. by ending the lifetime of the existing variable, hence transferring ownership), or with a borrow (a non-owning reference). So I don't think "move-only" is an exactly accurate description of these types; it's really "move-and-borrow-only". Alternatively, UniqueOwnership/UnshareableLifetime.

Fiiiiinally, we get to move itself. What this proposal is actually about. While copy is all about acquiring ownership, move allows us to relinquish ownership - to another variable, or not. They are counterparts - move is the release to copy's retain. That's why you had to read all of this, I'm afraid - I don't think we can address move until we address copy. We have to start there, so it can all be part of a consistent terminology (and I don't think the terms we have been using to date, or in the manifesto, necessarily give the best model for developers).

Anyway, I'm going to suggest endLifetime to mirror extendLifetime, and either giveOwnership or relinquishOwnership to mirror acquireOwnership.

Let's please not use the word 'transfer' for this. I still get flashbacks of __bridge_transfer.

In summary, the suggestions go something like this:

Ownership terminology:

  • copy = acquireOwnership
  • move = giveOwnership (heh, give or take? Depends on your perspective...)
  • @noImplicitCopy = @explicitOwnership (acquires must be explicit)
  • Copyable = ShareableOwnership
  • MoveOnly = UniqueOwnership

Lifetime terminology:

  • copy = extendLifetime
  • move = endLifetime
  • @noImplicitCopy = @explicitLifetime (extensions must be explicit)
  • Copyable = ShareableLifetime
  • MoveOnly = UnshareableLifetime
4 Likes