An Introduction to the Move Function in Swift for System Programmers
NOTE: In the following the word binding is referring to a let
or a var
like
entity in order to distinguish in between the “binding” itself and the “value”
contained within the “binding”. We never invalidate “values” using move
, only
invalidate “bindings”.
NOTE: It is assumed that the reader, as a prerequisite for reading this document, understands Swift’s copy on write model and how that interplays with uniqueness.
In Swift today, one is unable to end the lifetime of a binding and move its
contents into a different binding. With this in mind, the Swift team is adding a
new special function to Swift called move
. move
is inspired by functionality
like drop
from other languages like Rust. To work with move
, one applies
move
to a binding. move
will then invalidate the binding and emit an error if
one uses the binding ever again in the case of a let like construct (let
or
__owned
arg) or if one uses the binding before reinitializing it for a var
like binding (var
, inout
, mutating self
). As a simple example, consider a
routine that takes in a user data struct that contains a String that we wish to
preprocess and pass off to a system framework afterwards while having the
compiler enforce that uniqueness is preserved:
struct SystemFrameworkState {
var _innerState: String
var state: String { set { /* put newValue into _innerState and mutate _innerState */ } }
}
extension String {
init(data: UserData) { /* ... */ }
}
var globalState = SystemFrameworkState()
func f(input: UserData) {
// Construct a new string from user data.
let x = String(data: input)
// Use x in read only ways.
// Hand off x as an argument to another function while preserving uniqueness.
// We do not want to break uniqueness since we do not need more copies!
globalState.state = _move(x)
}
Without using move
here, we have two potential issues. The first is that we
are relying on the optimizer to notice that f
doesn’t use x
after passing
x
to globalState.state
. Of course most likely the optimizer will do so, but
when writing system code, “likely” is insufficient, so the guarantee allows us
to know that our requirement is maintained. Secondly and perhaps more
importantly, we are relying on our teammates to remember that they should not
add another use of x
when modifying this code. If they innocently change it to
globalState.state = x
x.doSomething()
they have accidentally forced Swift to copy x
, which is what we were trying to
avoid! By using move
we avoid both of these issues; the optimizer has a hard
guarantee that no copies are required, and code that would innocently introduce
a copy produces an error when compiling, like so:
globalState.state = _move(x)
x.doSomething() // Error! 'x' used after being moved.
move
also allows for one to rely on the compiler to safely move state from a
let
into a var
without breaking uniqueness of the value stored in the
let
. For example, consider the setter from SystemFrameworkState
above this
time with actual code:
struct SystemFrameworkState {
var _innerState: String
var state: String {
set {
// newValue is a __owned argument binding. We assume that it was passed in
// unique.
precondition(newValue.isUnique)
// Now we want to move it into a var so we can perform inout operations
// upon it. We move newValue so that the compiler will enforce at compile time
// that extra copies of newValue are not created since no further uses of newValue
// can occur in the function.
var x = _move(newValue)
// Now we can invoke mutating methods on x without causing a copy.
x.performMutatingMethod()
// And then store x into _innerState guaranteeing that if someone later
// puts more mutating code later on x, we get a compile time error.
_innerState = _move(x)
}
}
}
Notice, how we are able to move newValue
(which is a __owned
argument that behaves
like a let binding) into x
. We know that move
will prevent any later local
uses of newValue
, so we know that the value (now in x
) must still be unique. Thus
we can call mutating methods on the String without worrying about creating
copies.
Notice how in the previous paragraph, we mentioned that a __owned
argument
behaves similarly to a let
binding when it comes to move
. A natural question
then is to wonder how move
applies to inout
and mutating self
since such
arguments are in the language var
like arguments. In this case, move
behaves
in a similar way to var
, but has an additional constraint: the programmer must
re-initialize the inout
or mutating self
argument before the end of the
function. The reason for this restriction is that from an ABI perspective,
inout
and mutating self
have a requirement that even though one can move the
input argument out of the memory slot while the function is running, upon
return, a valid object must be in the argument slot. This allows for one to
safely move out self
or an inout
argument and later move in a new value
without needing to worry about future programmers forgetting to re-initialize
the value when adding new code. As an example, consider the following example
code that involves replacing a stored KlassState
with a new updated KlassState
from the kernel:
struct SystemFrameworkStruct {
var innerPtr: KlassState
mutating func updateState() {
// Grab the current inner ptr state off of the moved self.
let val = _move(self).innerPtr
// Now pass off our current inner ptr to the kernel, asking for
// a potentially different pointer to updated state:
let newValue = askKernelForNewPointer(val)
// If we don't reinitialize self we get an error! Uncomment next line to
// fix this.
// self = SystemFrameworkStruct(innerPtr: newValue)
}
}
Thus the user has communicated to the compiler using move
that after grabbing
innerPtr
, self has now been invalidated and must be reinitialized before the
end of the current function. This results in the following error being emitted:
test.swift:10:19: error: 'self' used after being moved
mutating func updateState() {
^
test.swift:12:19: note: move here
let val = _move(self).innerPtr
^
test.swift:21:5: note: use here
}
^
This property becomes even more important when working in the context of throwing code. Consider the following code where we want to updateState, but want to be able to throw:
struct SystemFrameworkStruct {
var innerPtr: KlassState
mutating func updateState() throws {
// Grab the current inner ptr state off of the moved self.
let val = _move(self).innerPtr
// Now pass off our current inner ptr to the kernel, asking for
// a potentially different pointer to updated state:
let newValue = askKernelForNewPointer(val)
// Call a throwing function to check if newValue is a safe value.
try checkNewValAndThrowIfBad(newValue)
// Reinitialize self here.
self = SystemFrameworkStruct(innerPtr: newValue)
}
}
Even though the code looks pretty reasonable, there is a hidden bug: the user
has not reinitialized self along the catch case of try causing a potential subtle
memory error if checkNewValAndThrowIfBad throws. By using move
though, we get a
compile time guarantee that self will be reinitialized before exit, so the compiler
properly flags that we did not clean up self along the catch path:
test.swift:10:19: error: 'self' used after being moved
mutating func updateState() throws {
^
test.swift:12:19: note: move here
let val = _move(self).innerPtr
^
test.swift:19:44: note: use here
try checkNewValAndThrowIfBad(newValue)
^
Another case where move
comes in handy is when one wishes to yield a unique
view to the internal state of a data structure and reinitialize self upon
return. Consider the following code taken from the open source project WebURL:
// CopyOnWrite URLStorage
struct URLStorage { /* ... */ }
internal let _tempStorage = URLStorage()
public struct PathComponents {
var storage: URLStorage
mutating func addComponent() { /* ... */ }
}
public struct WebURL {
var storage: URLStorage
public var pathComponents: PathComponents {
get { /* ... */ }
set { /* ... */ }
_modify {
var view = PathComponents(storage: storage)
storage = _tempStorage
defer { storage = view.storage }
yield &view
}
}
}
In this case, because the author does not have access to move
, they must use a
global instance of the URLStorage
class to enable it to move out its internal
storage while ensuring that storage
stays unique. Additionally, the author
needs to be able to guarantee that they do not forget to re-update storage with
the potentially mutated PathComponent
view. By using move
as below, one can
guarantee all of these properties:
public struct WebURL {
public var pathComponents: PathComponents {
get { /* ... */ }
set { /* ... */ }
_modify {
// Move out self ensuring view contains a unique URLStorage.
var view = PathComponents(storage: _move(self).storage)
// _move can look through defer and understand that it is going to
// reinitialize self before the end of the function.
defer { self = WebURL(storage: view.storage) }
// Then we yield view. view may be mutated by our caller and the defer
// and _move together ensure that self is reinitialize with the storage
// from the inside of the view.
yield &view
}
}
}
In conclusion, the move
function as shown above provides a new tool for Swift
programmers to precisely manipulate the lifetimes of values and control value
uniqueness in a way that was not possible before. The compile time performance
guarantees provided by move
will allow for system programmers to have
confidence that the code as written will perform well without needing to worry
about whether or not the optimizer kicked in. The author of the document hopes
that this new tool in the Swift system programmer tool box will enable the
flowering of new programs written in Swift providing benefit to the wider
community of computer users.