Values that must be used

RFC 0002 · Draft · Read first: Charter, Capabilities and effects

Every Rust value today can be dropped without a word and leaked with mem::forget, safely. In the new edition, a type can opt out: a transaction that must be committed or rolled back, a task handle that must be joined, an effect that must be handled. We follow the Rust 2026 goal's design and names, so this can go back upstream.1+

No comments yet.

Comments are not set up yet.

Where Rust is+

No comments yet.

Comments are not set up yet.

Rust's types are affine: a value is used at most once. Nothing makes you use it at least once. Drop runs at the end of scope, but mem::forget, Box::leak and reference cycles can skip it, and all of them are safe.+

No comments yet.

Comments are not set up yet.

That was decided in 2015, when a reference cycle through Rc let a scoped thread outlive its borrows (JoinGuard). Making leaks safe was the fix; guaranteed destructors went away with it.23+

No comments yet.

Comments are not set up yet.

Since then, APIs that must run cleanup keep ownership themselves: thread::scope(|s| ...) never hands the scope out. That works for sync code. It does not work for async, where the future is a value the caller can forget.4+

No comments yet.

Comments are not set up yet.

Proposal+

No comments yet.

Comments are not set up yet.

Adopt the hierarchy from the Rust 2026 project goal and Niko Matsakis's design, unchanged, in the new edition:14+

No comments yet.

Comments are not set up yet.

trait Forget: Destruct {}  // can be forgotten: mem::forget, Box::leak
trait Destruct: Move {}    // can be dropped implicitly at end of scope
trait Move: Pointee {}     // can be moved
trait Pointee {}           // any value
+

No comments yet.

Comments are not set up yet.

Every generic parameter keeps a default Forget bound, so every existing signature means what it means today. Writing a weaker bound opts out of the default, the way RFC 3729 does for Sized: T: Destruct accepts values that may not be forgotten, T: Move accepts values that must be consumed explicitly.5+

No comments yet.

Comments are not set up yet.

The borrow checker enforces it: a value going out of scope needs Destruct, a moved value needs Move, and mem::forget asks for Forget. ra-jit has its own borrow checker, so it can implement and test this now, on real code, without waiting for rustc.6+

No comments yet.

Comments are not set up yet.

struct Transaction { /* ... */ }
impl !Forget for Transaction {}
impl !Destruct for Transaction {}   // must call commit or rollback

impl Transaction {
    fn commit(self) -> Result<(), Error> { /* ... */ }
    fn rollback(self) { /* ... */ }
}

fn transfer(db: &Db) -> Result<(), Error> / Db {
    let tx = db.begin();
    tx.debit(a, 10)?;   // error: `tx` is dropped on this path
    tx.commit()
}
+

No comments yet.

Comments are not set up yet.

Names, syntax and the order of the traits follow upstream. Where upstream changes, this RFC changes with it.+

No comments yet.

Comments are not set up yet.

Why ra-jit wants it+

No comments yet.

Comments are not set up yet.

Prior art+

No comments yet.

Comments are not set up yet.

Open issues+

No comments yet.

Comments are not set up yet.

Panics and unwinding Open+

No comments yet.

Comments are not set up yet.

A panic between creating a !Destruct value and consuming it would drop it. Either such functions must not panic (pressure to track panics, see RFC 0001), or unwinding gets a separate cleanup path, or !Destruct code requires panic = abort.+

No comments yet.

Comments are not set up yet.

Rc and Arc cycles Open+

No comments yet.

Comments are not set up yet.

A cycle leaks without calling forget. Rc<T> and Arc<T> need T: Forget, or something weaker that still rules out cycles.+

No comments yet.

Comments are not set up yet.

Which std bounds to weaken Open+

No comments yet.

Comments are not set up yet.

Vec, Option, Box and channels should accept !Forget values, but every container that can leak (a Vec you mem::forget) has to be checked. Upstream's goal does this work; we take its answers.+

No comments yet.

Comments are not set up yet.

Old-edition generics Open+

No comments yet.

Comments are not set up yet.

Generic code in editions 2015 to 2024 assumes Forget everywhere. A !Forget type can only be passed to it if it does not actually forget; the default bound stays, so it cannot, and old crates are not usable with these types until they relax their bounds.+

No comments yet.

Comments are not set up yet.

Closures and captures Open+

No comments yet.

Comments are not set up yet.

A closure that captures a !Destruct value is itself !Destruct, and an FnMut that may run zero times cannot consume it. How do Fn traits carry these bounds?+

No comments yet.

Comments are not set up yet.

Futures and cancellation Open+

No comments yet.

Comments are not set up yet.

Dropping a future cancels it. A !Destruct future cannot be cancelled by drop. That is the point for scoped tasks, but executors and select! assume they can drop futures. Upstream leaves the Future trait out of scope for now.+

No comments yet.

Comments are not set up yet.

The function cache Open+

No comments yet.

Comments are not set up yet.

A cached call returns a stored copy of its result. A !Forget or !Destruct result cannot be duplicated or cached; the ra-jit: the function cache has to refuse such functions.+

No comments yet.

Comments are not set up yet.

References

  1. Rust 2026 goal: Move trait The project goal to add the trait hierarchy below Sized.
  2. Niko Matsakis, On reference counting and leaks (2015) Why mem::forget became safe.
  3. rust-lang/rust#24292: JoinGuard is unsound because of reference cycles The 2015 bug that made leaking safe.
  4. Niko Matsakis, Move, Destruct, Leak The proposed hierarchy, from types that cannot move to types that may be leaked.
  5. rust-lang/rfcs#3729: hierarchy of Sized traits Open. The ?Trait machinery the new bounds would reuse.
  6. ra-jit: borrow checking
  7. Perceus: garbage-free reference counting with reuse Unique values updated in place by pure code.
  8. rust-lang/rust#149607: tracking issue for Move Open.
  9. rust-lang/rfcs#814: add a linear type facility Open since 2015.
  10. rust-lang/rfcs#3782: Forget marker trait Open.
  11. rust-lang/rfcs#3867: forgettable auto trait Closed 2025-10-05.
  12. Yoshua Wuyts, linear types one-pager
  13. Effects initiative: unleakable types
  14. Aria Desires, The pain of real linear types in Rust Why it is hard: unwinding, generics, every std API.
  15. without.boats, Changing the rules of Rust Adding a new auto trait across editions.
  16. Austral A systems language with linear types and capabilities.
  17. GHC LinearTypes
  18. ra-jit: the function cache #[ra_jit::cache], and what makes a function ineligible.

Discussion

Comments on the page as a whole. To comment on one paragraph, use the button beside it.

Comments are not set up yet.