Rust 1.95 stabilises Linear Types via the MustMove trait, plus native Pin projections. A deep-dive into the new use-exactly-once model and migration. Read now.
What MustMove Actually Changes
Rust 1.95 stabilises Linear Types through the MustMove trait: a value marked this way must be consumed exactly once. You cannot drop it quietly, forget it in a branch, or leave it sitting in a local that goes out of scope without an explicit move into a sink function. That is stricter than ordinary ownership, which already prevents use-after-move and double-free but still allows you to abandon a value by dropping it.
The practical target is resources where “forget to finish” is as bad as “use twice.” Think protocol handshakes that must send a terminal frame, file handles that must flush and close on a defined path, or state machines where intermediate states are invalid if left live. MustMove makes the compiler reject those incomplete paths at build time instead of relying on Drop, lints, or review.
The Use-Exactly-Once Model
Under the use-exactly-once model, every construction of a linear value has a matching consuming operation. Pattern matching and control flow still work, but each arm that ends the value’s life must move it into a function that takes ownership and does not return it. Returning the value from a helper is fine; “falling off the end” of a block is not, unless the type’s drop is itself a valid linear sink—and for true linear types it usually is not.
This pairs cleanly with APIs that already encode state in types (builder → sealed config, open → closed connection). Linear Types close the gap where a half-finished builder could still be dropped without calling build. You design the terminal methods as the only legal exits, and the type system enforces that callers hit one of them.
Native Pin Projections
Native Pin projections land alongside Linear Types and remove a long-standing ergonomic tax: projecting fields through Pin<&mut Self> without unsafe glue or external macros. When a struct is pinned, you can obtain pinned references to its fields under the same rules the language already uses for structural pinning—field by field, without inventing ad-hoc projection helpers for every type.
That matters for futures, intrusive lists, and self-referential buffers where the address of a field must stay stable. Less boilerplate around Pin means fewer places for projection mistakes and clearer intent in async and systems code that already depends on immovable data.
- Prefer
MustMoveon types whose incomplete lifetime is a correctness bug, not only a resource leak. - Expose one or a few consuming APIs as the only way out of a linear value.
- Use native Pin projections when a field must stay pinned with the parent; avoid manual projection unless you need a custom invariant.
- Keep non-linear types as the default; linear ones should be narrow and intentional.
Migration Without a Big Rewrite
Adopt Linear Types at API boundaries first. Introduce a linear wrapper or a newtype around the critical resource, migrate callers that construct and finish it, then expand inward. Existing code that only needs ordinary ownership does not need to change. Where a value was previously cleaned up in Drop, decide whether drop is still a valid sink or whether you want drop to be impossible so every path ends in an explicit finish method.
For Pin-heavy crates, swap custom projection helpers for native projections where the structural-pinning story matches your layout. Keep unsafe projection only when your invariants go beyond what the language guarantees. Treat 1.95 as a chance to encode “must complete” and “must not move” in the type system—not as a reason to linearise every type in the crate.