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.