Stack-allocated objects are used very often in Rust, and they're not limited to a strict LIFO usage.
Rust doesn't have C++'s RAII with destructors running unconditionally at the end of scope. In Rust, destructors run only once after the last use of a value, even if the value moves between scopes.
In the article's terms, Rust uses stack allocation wherever possible, but can also pass objects by value to make the complex non-LIFO uses possible (which is easier in Rust, because it doesn't identify objects by their address, doesn't need a pointer indirection to ensure uniqueness, and lifetimes ensure there are no dangling pointers to the old address).
Thread-local arenas are possible, and Rust can enforce that the objects never leave the thread (!Send types).
It's also possible to create memory arenas in any scope and use lifetimes to ensure that their objects can't leave the scope.
However, placing objects with destructors in arenas is tricky. Since the whole point is to let objects easily reference each other, there's nothing stopping them from trying to access the other objects in the same arena while the arena is being destroyed and runs the destructors. That's an inherent issue with arenas, in C too if the arena supports some kind of per-object cleanup.
I like your response the most among your siblings.
A couple more points, based on how the bumpalo crate does it:
- If you don't really need destructors, then it's fine to just not call them. This works for things like strings, lists of strings, objects with references to each other, etc.
- If you do need a destructor, you can create a stack object that references and allocates into the arena, and it will be the stack object whose purpose is to run that destructor (but deallocation happens later). This preserves drop ordering while still getting some benefit of arena allocation.
Rust doesn't have C++'s RAII with destructors running unconditionally at the end of scope. In Rust, destructors run only once after the last use of a value, even if the value moves between scopes.
In the article's terms, Rust uses stack allocation wherever possible, but can also pass objects by value to make the complex non-LIFO uses possible (which is easier in Rust, because it doesn't identify objects by their address, doesn't need a pointer indirection to ensure uniqueness, and lifetimes ensure there are no dangling pointers to the old address).
Thread-local arenas are possible, and Rust can enforce that the objects never leave the thread (!Send types).
It's also possible to create memory arenas in any scope and use lifetimes to ensure that their objects can't leave the scope.
However, placing objects with destructors in arenas is tricky. Since the whole point is to let objects easily reference each other, there's nothing stopping them from trying to access the other objects in the same arena while the arena is being destroyed and runs the destructors. That's an inherent issue with arenas, in C too if the arena supports some kind of per-object cleanup.