Rendered at 11:45:42 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
panstromek 1 hours ago [-]
For everybody who doesn't have the context, just note that this is not an accepted langauge change. It's a just project goal, which means it's accepted as something people will work on, but the design might change significantly or it can even be abandoned completely (which is pretty unlikely for this one, to be fair).
stymaar 4 hours ago [-]
Great new! Since 2016 or so it became apparent that immovable types were a crucial missing part of Rust, but for a long time it was believed it wouldn't be possible to add them without breaking everything, which is why we ended up with the Pin hack.
I'm very glad they found a way to add it eventually, as it's really filling a glaring hole in the language.
q3k 2 hours ago [-]
> I'm very glad they found a way to add it eventually
Will this integrate with existing code that uses Pin<T>? If not this will split the ecosystem even further...
ordu 1 hours ago [-]
I don't see why it may fail to integrate. Declare Pin as !Move and... thats all? I mean, there will be issues, edge-cases because it is just how these things happen, but still I don't see any fundamental issues with continuing to use Pin.
yccs27 4 hours ago [-]
There's a different proposal by @withoutboats to make immovability a property of the place/reference instead of the type:
Does this project goal mean that the rust maintainers have decided to implement @yoshuawuyts' immovable types proposal in favor of pinned places?
rienbdj 3 hours ago [-]
This sounds like a similar approach to OxCaml
Ygg2 3 hours ago [-]
> # How does this relate to the "pin ergonomics" initiative?
> This work is an alternative to Project Goal 2025H2: Continue Experimentation with Pin Ergonomics, which includes the following extensions:
> A new item family pin in lvalues, e.g. &pin x, &pin mut x, &pin const x.
> A one-off overload of Rust's Drop trait, e.g. fn drop(&pin mut self).
> A new item kind pin in patterns, e.g. &pin <pat>.
> Notably, this work does not solve pin's duplicate definition problem, meaning that even with these extentions we still end up with Trait and PinnedTrait variants of existing traits. The Drop trait being the exception to this, since the initiative is proposing to special-case it using a one-off overload.
Ah, thanks, I didn't realize "pin ergonomics" was the Rust Project name for @withoutboats' pinned places.
Tazerenix 5 hours ago [-]
More algebraic effects being retrofitted onto Rust.
dubi_steinkek 3 minutes ago [-]
How so? This feel distinct from the "algebraic effects"-like features like constness, async, can-panic, can-unwind, etc., since this is a property of the types themselves rather than of functions.
skitter 4 hours ago [-]
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.
simonask 3 hours ago [-]
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.
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.
ordu 50 minutes ago [-]
> 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:
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.
vlovich123 22 minutes ago [-]
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?
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).
dubi_steinkek 1 minutes ago [-]
Which stdlib collections and types should become `!Move`?
virtualritz 2 hours ago [-]
But Rust has editions.
That is a big lever language designers can use if they painted themselves into a corner.
yccs27 5 minutes ago [-]
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.
OskarS 4 hours ago [-]
mem::forget isn’t the only way you can safely leak a value, you can do it with reference cycles too, right? And there is no way for the compiler to detect that?
Isn’t that why mem::forget is safe, because you can always implement it yourself safely? How do you get around that?
stymaar 4 hours ago [-]
> mem::forget isn’t the only way you can safely leak a value, you can do it with reference cycles too, right? And there is no way for the compiler to detect that?
But there's an easy solution for that: you make the reference-counted smart pointers require their pointee type to be Forget. It will be like how Arc<T> doesn't implement Send unless <T: Sync>.
skitter 4 hours ago [-]
By doing the same as with `Sized`: Automatically including the `Forget` bound on generic parameters and letting methods that don't need to be able to forget them opt out. That way existing code continues to compile and existing unsafe code doesn't become unsound.
suddenlybananas 5 hours ago [-]
Could someone explain this to me as someone who's never touched async Rust? What kind of useful patterns would this allow for?
simonask 4 hours ago [-]
The big one is scoped tasks, or structured concurrency.
Currently, Rust has scoped threads: Threads that are guaranteed to terminate before the function that spawned them returns. This is powerful because it allows you to pass references to data that lives on your own stack to threads that you spawn, without any bookkeeping or synchronization mechanism - just the normal borrow checker rules.
For example, you can allocate a large array, then split it into multiple non-overlapping slices, and then have a group of threads populate each slice, all in safe Rust code.
But the same isn't true for async tasks in Rust, because futures are just objects representing a state machine, and they don't get any special treatment. In particular, they carry no guarantee that the state machine will actually run to completion, which is fundamentally different from how functions run (stack frames are guaranteed to unwind in some way, either by returning or panicking, unless the entire program has terminated).
To make the situation worse, there are many cases where Rust futures are much more prone to cancellation than synchronous code, because that is also one of the big benefits of using async in the first place - for example, you may be running multiple futures in parallel, pick the result from the one that finishes first, and then cancel the rest.
Getting this stuff under control is why people say that "async cancellation" is a difficult problem to solve, and that is true in all languages that have async. These traits will hopefully make it much easier to work with in Rust.
(There are also many other interesting things you could do with this, unrelated to async. Immovable and unforgettable are both interesting properties of an object that could be used to design many cool APIs in general.)
pornel 24 minutes ago [-]
Things you'd expect to work already.
It doesn't really add anything new and flashy, but removes some annoying warts.
Sync code has scoped threads that enable multi-threaded execution within a function, without having to ensure the data outlives the function call. Async can't do that while guaranteeing safety. This makes tokio::spawn awkward and annoying, and is a major source why people dislike Rust's async.
Low-level async code that polls Futures requires using the Pin wrapper type, which is unergonomic, and doesn't really guarantee safety, but it's more like a "be careful here" sign. Proposed changes would make that code look more like normal Rust and work without unsafe escape hatches.
aabhay 4 hours ago [-]
It makes it easier to write recursive async functions. It makes it easier for async functions to borrow rather than clone from their outer scope.
All really awesome, non controversial and ergonomic things.
I'm very glad they found a way to add it eventually, as it's really filling a glaring hole in the language.
Will this integrate with existing code that uses Pin<T>? If not this will split the ecosystem even further...
https://without.boats/blog/pinned-places/
Does this project goal mean that the rust maintainers have decided to implement @yoshuawuyts' immovable types proposal in favor of pinned places?
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.
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.
Isn’t that why mem::forget is safe, because you can always implement it yourself safely? How do you get around that?
But there's an easy solution for that: you make the reference-counted smart pointers require their pointee type to be Forget. It will be like how Arc<T> doesn't implement Send unless <T: Sync>.
Currently, Rust has scoped threads: Threads that are guaranteed to terminate before the function that spawned them returns. This is powerful because it allows you to pass references to data that lives on your own stack to threads that you spawn, without any bookkeeping or synchronization mechanism - just the normal borrow checker rules.
For example, you can allocate a large array, then split it into multiple non-overlapping slices, and then have a group of threads populate each slice, all in safe Rust code.
But the same isn't true for async tasks in Rust, because futures are just objects representing a state machine, and they don't get any special treatment. In particular, they carry no guarantee that the state machine will actually run to completion, which is fundamentally different from how functions run (stack frames are guaranteed to unwind in some way, either by returning or panicking, unless the entire program has terminated).
To make the situation worse, there are many cases where Rust futures are much more prone to cancellation than synchronous code, because that is also one of the big benefits of using async in the first place - for example, you may be running multiple futures in parallel, pick the result from the one that finishes first, and then cancel the rest.
Getting this stuff under control is why people say that "async cancellation" is a difficult problem to solve, and that is true in all languages that have async. These traits will hopefully make it much easier to work with in Rust.
(There are also many other interesting things you could do with this, unrelated to async. Immovable and unforgettable are both interesting properties of an object that could be used to design many cool APIs in general.)
It doesn't really add anything new and flashy, but removes some annoying warts.
Sync code has scoped threads that enable multi-threaded execution within a function, without having to ensure the data outlives the function call. Async can't do that while guaranteeing safety. This makes tokio::spawn awkward and annoying, and is a major source why people dislike Rust's async.
Low-level async code that polls Futures requires using the Pin wrapper type, which is unergonomic, and doesn't really guarantee safety, but it's more like a "be careful here" sign. Proposed changes would make that code look more like normal Rust and work without unsafe escape hatches.
All really awesome, non controversial and ergonomic things.