What does the consume operator do for ordinary values, and does it release a class instance earlier?
Predict first
What does this print?
final class T {
let n: String
init(_ n: String) { self.n = n }
deinit { print("deinit \(n)") }
}
func moved() {
let x = T("x")
print("1")
let y = consume x
print("2", y.n)
print("3")
}
moved()
func reassigned() {
var v = T("first")
print("A")
v = T("second")
print("B")
_ = v
print("C")
}
reassigned()
- 1, 2 x, 3, deinit x, A, deinit first, B, C, deinit second
- 1, deinit x, 2 x, 3, A, B, deinit first, C, deinit second
- 1, 2 x, deinit x, 3, A, deinit first, B, deinit second, C
Mental model. consume is writing “moved to a new address” on the old mailbox: nobody may deliver there any more, but the parcel is still in the house until the new owner throws it away.
let y = consume x ends the variable x: using it again is a compile error (“'x' used after consume”). It tells the compiler to move the value instead of copying it, which can avoid a retain/release pair or a copy-on-write copy, and it is how you hand ownership of a noncopyable value on. For ordinary copyable values it does not promise an earlier deinit: in the check the object moved into y is still destroyed at the end of the function (deinit x after 3), because y now owns it and lives to the end of its scope. To release a class instance early, end the last owner: assign nil or a new value to the variable, or let the scope end.
The second half of the check shows the common way lifetimes end: assignment releases the old value immediately, so deinit first prints before B, while the new object lives until the end of the function even though _ = v is not a real use. This is the same lexical-lifetime behaviour as in Q130, with one caveat: it is the compiler’s optimisation choice and the optimiser may shorten it for plain locals in some builds, so do not build correctness on exact timing. Where you do need a guarantee, use scope (do { }), explicit nil, withExtendedLifetime to keep something alive, or a noncopyable type to make the end of life part of the type.
consume only ends the name x; the new owner y keeps the object to the end of the scope. Assignment releases the old value at once.
Common traps
- Using
consumeas a way to free memory. It ends use of a name, not the lifetime of the object the new owner holds. - Reading the order of
deinitprints in a debug build as a contract. The compiler may shorten lifetimes of unused values in optimised builds.
They might follow up with
- Is there a
copyoperator too? Yes, to make an explicit copy where the compiler would otherwise move or reject. - Does
consumework on globals and captured variables? No; it applies to local variables, parameters and bindings.
Related questions
- 3.8.4 How do noncopyable types behave in practice: deinit on a struct, consuming and borrowing methods, discard…
- 3.1.3 When exactly is an object deallocated: at the end of its scope, or after its last use?
- 3.6.2 What do guaranteed and owned parameters mean, and what do borrowing and consuming add?
- 1.8.5 What are consume, borrowing, and noncopyable types, at a high level?
← How do noncopyable types behave in practice: deinit on a struct…