Thank you for explaining—I agree that withoutActuallyEscaping
is another good comparison.
The difference, in my mind, between move
as-proposed and withoutActuallyEscaping
(as well as withExtendedLifetime
, and type(of:)
two other 'pseudo-functions' that have been mentioned) is that ultimately, they all behave basically like 'true' Swift functions: they accept a value, and produce an output in accordance with all the usual rules of Swift semantics, albeit with some additional type system support that can't currently be spelled in a function signature (e.g., to make sure that the argument to withoutActuallyEscaping
is of function type).
For instance, the fact that escaping function types have a subtype relationship with non-escaping types means that all of the following compile, even though they are a bit silly:
func f(g: @escaping () -> Void) {
withoutActuallyEscaping(g) { escapingClosure in
print(escapingClosure)
}
}
func makeF() -> () -> Void { {} }
f(g: {})
withoutActuallyEscaping(makeF()) { escapingClosure in
print(escapingClosure)
}
withoutActuallyEscaping({}) { escapingClosure in
print(escapingClosure)
}
Furthermore, potential future language enhancements could make withoutActuallyEscaping
much more expressible in the type system. E.g., with variadic generics the signature of withoutActuallyEscaping
could look something like:
public func withoutActuallyEscaping<Args..., InResultType, OutResultType>(
_ closure: (Args...) -> InResultType,
do body: (_ escapingClosure: @escaping (Args...) -> InResultType) throws -> OutResultType
) rethrows -> OutResultType
However, move
differs in a couple of important ways. For one, it does not operate on Swift values. It operates on bindings, and even more specifically on bindings to storage. These are not concepts that the type system exposes to users in any form today, and I am not aware of any plans to expose such concepts to the type system. The type semantics of move
are trivial (it's the identity function), but move
has implications at both the syntactic level ('is the argument written as a binding?') and a deeper semantic level ('does the binding refer to actual storage?').
Additionally, the move
'function' has effect on the semantics of the code exterior to the call of move
itself. AFAICT, this does not apply to withExtendedLifetime
or withoutActuallyEscaping
whose effects (and all with
-style functions) are explicitly constrained to the body of their closure argument.
IMO these are strong reasons to make move
a piece of syntax—its integration with the language is much deeper than is able to be expressed by a stdlib function or the Swift type system.
Suppose we were proposing the guard
functionality before it was around, and suggested an stdlib function with the following signature (and lifting the restriction variadic auto closures... ):
func guard(conditions: @autoclosure (() throws -> Bool)..., else: () throws -> Void) rethrows
But that doesn't give us all the power we want from guard
, so we additionally propose some 'extra semantics': each of the conditions
is permitted to be a pattern, bindings introduced by each of conditions
are introduced in the outer scope, and if the else
block is executed then guard will return control to the caller two levels up.
Sure, we could have approximated the language-construct version of guard
with a function, but it would have seemed very weird to me to pretend that it's 'just' a library function when its interaction with the language and the calling function is really much more fundamental. IMO it's the same for move
(though I admit move
as a function is a bit less ridiculous than my analogy ).
EDIT: to make my point a bit more directly (sorry for the rambling post), I think that expressing move
as a function actively interferes with the understanding of the feature. Under Swift's function call semantics, as I understand it, both the T
passed in to move
and the T
returned from move
should be (semantic) copies, but move
is very specifically not copying its argument/return value.