Hacker News new | past | comments | ask | show | jobs | submit
Although not part of the goal, it also mentions `!Destruct`/"must-move types", aka linear types: Instead of there always being a way to drop values without providing any arguments, if you wanna get rid of a value of a linear type you have to call a function that takes it by value.
For context, the reason this would be really nice is that it would enable API designs that catch certain kinds of errors.

    let txn = create_transaction();
    // do something with the transaction
    txn.commit(); // consume the txn
Right now, you can't implement this API without choosing between either silently rolling back unless the user calls `commit()`, or panicking in the Drop impl for the transaction if the user didn't explicitly call either `commit()` or `rollback()`.

Your only current choice is to use closures, which are much less composable, because you need a variant for each flavor: infallible, fallible, async fallibe, etc.

    start_transaction_async(async || { /* ... */ TransactionResult::Commit });
    start_transaction_async_try(async || { /* ... */ Ok(TransactionResult::Commit });
Ick.

If instead the transaction is a must-move type, you would get a compiler error if you fail to call exactly one of either commit or rollback, and particularly you would be forced to consider what happens at every exit point (early-out via `?` no longer just forgets the transaction). Very nice.

> If instead the transaction is a must-move type, you would get a compiler error if you fail to call exactly one of either commit or rollback

Can you elaborate how it may work? I mean if I create a function:

fn fail_silently(txn: Transaction) {}

then the calling code would pass the compiler, but this function presumably isn't, ok. But what can make these functions to pass:

impl Transaction { pub fn commit(self) { ... } pub fn rollback(self) { ... } }

Would you need to destructure self or what?

Yes, destructuring is typically the only allowed way to get rid of linear/indestructible values. If the type has private fields, this is only possible in the same module, so commit(txn) and rollback(txn) would have to be implemented in the same module as the Transaction type.
Exactly - fail_silently is illegal and you have to actually destructure the type to explicitly implement the destructor

> How would you handle destructors with arguments?

https://smallcultfollowing.com/babysteps/blog/2025/10/21/mov...

Linear types requires significant work to incorporate into the core built-in collections and types. I've been following the work on Mojo to enable Linear type support for built-in types and collections, I don't think Rust's language semantics will allow for the same level of integration (Rust is already stable).
But Rust has editions.

That is a big lever language designers can use if they painted themselves into a corner.

Yes, editions are a great mechanism. It still has its limits, especially if you want easy edition migrations. All existing Rust code assumes it can drop any type whenever it wants, and that is not something you can just change across editions. You have to be very careful with defaults if you don't want conflicts when crossing edition boundaries.
The naïve idea would be to just say that all generic parameters have an implicit `where T: Move` bound, and you have to explicitly opt out of it with `where T: ?Move`, just like with `?Sized`.

In fact, that's exactly how I would expect it to work, but there may be non-obvious drawbacks.

The compat issue has always been associated types on std traits.

For example, should Iterator::Item be Move or ?Move

If you leave it as Move, you can't create any iterators over !Move types. If you change it to ?Move, then functions using generic iterators can't assume that the elements of an Iterator are always moveable. Which is a breaking change compared to now.

The most critical trait is probably Deref. Using !Move types without `Deref::Target: ?Move` is painful, because calling any method on boxed types relies on Deref.

The people working on this are aware that it poses backcompat problems that don't have obvious solutions. They are looking into non-obvious solutions. https://lcnr.de/blog/2025/11/28/implicit-auto-traits-assoc-t... is the most up-to-date one I'm currently aware of.
I'm very probably missing something, but as a user I would definitely expect `Iterator::Item: Move`, but then also that `&{mut} T: Move where T: ?Move`.

But yeah I can see how these bounds are somewhat viral. Thanks!

Here is an example of the problem: https://play.rust-lang.org/?version=stable&mode=debug&editio...

Imagine if MyTrait comes from core/std. Adding an opt-out bound like ?Sized (or ?Move) is a breaking change for any generic code that relies on Sized/Move. But you want some traits from std to be open for !Move types.

Which stdlib collections and types should become `!Move`?
None. The question is more what happens when you put a `!Move` type into, say, `Vec<T>`, because that's a collection type that regularly moves its elements to a new allocation when it grows or shrinks.

Should it be possible to construct a `Vec<T>` whose size can never change? Is there a subset of Vec's API that can be annotated with `where T: ?Move`? These are all important design questions, with the potential to break 99% of existing Rust code.

We already have a Vec<T> whose size can't change, it is called Box<[T]>. So Vec doesn't need to support !Move types.