docs / Getting Started / Use-After-Move

Use-After-Move & Escape Analysis

Once you pass a resource to a function, the compiler refuses to let you use that variable again. This is enforced statically — there is no runtime check, just a compile error.

use system.io

// Resource types (those defining `fn drop(self)`) are tracked strictly.
// Once you pass one to a function, you can't use it again.
class Connection
    public id int

    fn init(i int)
        self.id = i

    fn drop(self)
        println(f"closing {self.id}")

fn archive(c Connection)
    println(f"archiving {c.id}")

fn main()
    let c = Connection(i: 1)
    archive(c)
    // archive(c)   // compile error: 'c' was consumed by 'archive'
                    // and cannot be used again

    // Need to call archive twice? Use `.clone()` to opt in to a copy
    // (only available for Cloneable types — resource types are
    // intentionally not Cloneable, because cloning a file handle is
    // almost always a bug).

The rule has two layers:

  • Resource types are tracked strictly at every scope. Pass one to a function, store it in a field, or alias it via assignment, and the original binding is marked consumed. Any subsequent use is a compile error with diagnostic E0110.
  • Managed types are tracked at the top level (script-style code) and inside function bodies via escape analysis. The compiler examines each callee's body to see whether your argument is just being read (a borrow) or genuinely escapes — returned, stored on the heap, or captured into a returned closure. Reads do not consume. Escapes do. The diagnostic explains the multi-hop chain that led to the move.

The fix is always the same: .clone() the value before the consuming call. Because escape analysis is precise, you only need to clone at the spots that actually matter — the borrow-checker tax of "clone everywhere just in case" is gone.