What does the consume operator do for ordinary values, and does it release a class instance earlier?

3.8.5 · L5 · Interview

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 consume as 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 deinit prints 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 copy operator too? Yes, to make an explicit copy where the compiler would otherwise move or reject.
  • Does consume work 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…