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.
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?
> How would you handle destructors with arguments?
https://smallcultfollowing.com/babysteps/blog/2025/10/21/mov...
That is a big lever language designers can use if they painted themselves into a corner.
In fact, that's exactly how I would expect it to work, but there may be non-obvious drawbacks.
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.
But yeah I can see how these bounds are somewhat viral. Thanks!
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.
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.