-
Notifications
You must be signed in to change notification settings - Fork 1.6k
de-RFC: Remove unsized_locals #3829
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f951ce6
9347bb5
b48b123
abb5788
a104109
5bc1184
0e0b1a5
969b139
571ff4d
7a8424e
c0c6b67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,163 @@ | ||||||
- Feature Name: `unsized_locals` | ||||||
- Start Date: 2025-06-02 | ||||||
- RFC PR: [rust-lang/rfcs#3829](https://github.com/rust-lang/rfcs/pull/3829) | ||||||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||||||
|
||||||
|
||||||
_Following the great success of the [type ascription de-RFC], we now present the next one in the series!_ | ||||||
|
||||||
# Summary | ||||||
[summary]: #summary | ||||||
|
||||||
Unsized locals ([RFC 1909], called "unsized rvalues" originally) | ||||||
has been a merged RFC for eight years with no clear path to stabilization. | ||||||
|
||||||
There is still a very large gap in the implementation in rustc that hasn't been addressed for many years and there are several language problems with this feature. | ||||||
|
||||||
This RFC intends to advocate for the feature being removed entirely with a fresh RFC being necessary to add a similar feature again. | ||||||
|
||||||
Note that the acceptance of this RFC should not be taken as judgement on a future RFC. A fresh RFC with a new design would be required, and this RFC may provide input on design constraints for such an RFC, but this should not be taken as pre-rejecting such an RFC. | ||||||
|
||||||
# Demotivation | ||||||
[demotivation]: #demotivation | ||||||
|
||||||
The `unsized_locals` feature is simple to explain on the surface: local variables no longer have to be `Sized`. | ||||||
|
||||||
```rust | ||||||
#![feature(unsized_locals)] | ||||||
fn main() { | ||||||
let x = *Box::from("hello, world!"); | ||||||
} | ||||||
``` | ||||||
|
||||||
This will dynamically allocate space on the stack for the string. | ||||||
C has a similar feature, [`alloca`] and variable length arrays (VLA), the latter having been made optional to implement in C11. | ||||||
|
||||||
## Lack of Proper Implementation | ||||||
|
||||||
This feature has never been properly implemented in rustc. | ||||||
The variable length array form proposed in the RFC still doesn't exist at all, only the unsized local variable does. | ||||||
It is implemented in the type checker and codegen, but [lacking MIR semantics](https://github.com/rust-lang/rust/issues/48055#issuecomment-1837424794) and therefore unimplemented in the compile time function evaluator. | ||||||
This is very significant, as MIR semantics govern how the feature should behave precisely in the first place. | ||||||
Without them, they cannot work in `const` and optimizations are likely broken around them. | ||||||
Because of this lack of implementation quality, the `unsized_locals` feature was already accepted for removal from rustc two years ago in [MCP 630]. | ||||||
This removal hasn't yet been implemented. | ||||||
|
||||||
Dynamic stack allocation as currently implemented interacts especially poorly with loops. | ||||||
Allocations are not freed until the function returns, so the following example overflows the stack: | ||||||
|
||||||
```rust | ||||||
#![feature(unsized_locals)] | ||||||
fn main() { | ||||||
let s = "A".repeat(1000); | ||||||
for i in 0..1000000 { | ||||||
let x: str = *Box::from(s.as_str()); | ||||||
std::hint::black_box(&s); | ||||||
} | ||||||
} | ||||||
``` | ||||||
There are ways around this (rewinding the stack pointer at the end of the loop to free up the memory), but they are not currently implemented. | ||||||
Noratrieb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
## Implicit Danger | ||||||
|
||||||
While lack of implementation quality is a sign of lack of interest for the feature, it is not the primary reason for this RFC, | ||||||
which is purely about the language design of the feature. | ||||||
|
||||||
The original RFC was very short, and especially short on motivation and rationale for the design. | ||||||
|
||||||
Dynamic stack allocation in general has a rather significant downside: it makes it easy to accidentally overflow the stack if you allocate a lot. | ||||||
Stacks are usually rather small (on the order of few megabytes, depending on the platform), which means that dynamically allocating user-controlled input on the stack is often rather dangerous. | ||||||
While stack overflows are not considered memory unsafe by Rust, they still cause crashes which can lead to denial of service vulnerabilities and unreliability in general. | ||||||
|
||||||
Dynamic stack allocation also has its upsides. | ||||||
It is generally faster than heap allocation and can therefore improve performance in cases where the previously mentioned downsides are not a concern. | ||||||
Therefore, this RFC is not necessarily a rejection of the idea of dynamic stack allocation, but merely the way the `unsized_locals` feature exposes it. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that we still need to figure out the best way to expose the feature, but I think that the best way to do that is to implement it properly on nightly and enable experimentation. Right now, we have vague concerns about footguns and blowing the stack, but without concrete user feedback, it’s impossible to determine how severe the issue is or how the problem could be mitigated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E.g. maybe lints could address the danger. Or maybe all unsized locals should be annotated in some way (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then go implement it if you think it's such a good idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point is, it’s hard to experiment with the frontend/syntax parts of the feature, if the codegen/backend is missing/broken. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No significant experimentation has happened for the past eight years. I wouldn't hold my beer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, it's actually the middle-end that we're missing, I think? Because MIR is kinda its own beast? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, because the middle/back-end implementation is broken, scaring nightly users away. If the compiler team would rather strip out that broken implementation than continue to let it rot, I have no objection. But rust-lang/compiler-team#630 is enough for that. IIUC this de-RFC would require anyone motivated to implement the feature properly, to go through the full RFC process first. I think that’s going too far. My preference would be to “downgrade” #1909 to an eRFC, instead of unaccepting it completely. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing it to an "experimental" RFC will not change that right now it is a gravestone of a feature that no one wants to proceed on. Yes, probably someone should go through the RFC process again, because right now there's not even a draft of a proposal for how it may be implemented in rustc's semantic layers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I wouldn’t say that. For example, someone recently put in the effort to fix the longstanding issue with unsized locals being misaligned. Clearly there are some people still interested in this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe that what @tmiasko was touching on there involved fixes to parts of the code that were shared between Also see: |
||||||
|
||||||
Which is where we get to the major argument of this RFC: The `unsized_locals` feature integrates far too naturally into Rust. | ||||||
This makes the feature very **easy to use**, and especially **easy to use accidentally**. | ||||||
|
||||||
As previously mentioned, dynamic stack allocation can be dangerous and should not be used lightly. | ||||||
It's an advanced optimization feature that is best left untouched by default. | ||||||
As such, it behaves similarly to `unsafe` (but is not actually `unsafe`). | ||||||
Comment on lines
+79
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this exaggerates the danger. For example, maybe you are writing code for an embedded system with a low amount of memory. You want to use trait objects to save on code size, but also don’t want to pull in an allocator. In this case, dynamic alloca could be very helpful for reducing memory usage. And, as others have mentioned, there are many other safe and easy ways of overflowing the stack in Rust. We don’t generally call e.g. recursion an “advanced optimization feature” that’s so scary it’s “similar to |
||||||
With `unsized_locals`, the use of dynamic stack allocation is completely implicit. | ||||||
When you create an unsized local, it is often not obvious that dynamic stack allocation is happening. | ||||||
workingjubilee marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
In the example from the start, we need to be aware of all the involved types (which are often inferred in practice) to know that this is a potentially problematic unsized local that we have to audit more carefully instead of a normal sized local. | ||||||
Especially around strings, which are often user-controlled, this easily leads to accidentally-dangerous situations. | ||||||
|
||||||
Rust's strings, reference types, and `Sized` are a part of the language that can often be hard to understand for beginners coming from garbage-collected languages. | ||||||
By allowing people to create a dynamic stack allocation without being aware of what is happening, we open the doors for people, especially new Rust programmers who are not intimately familiar with the tradeoffs of dynamic stack allocation, to shoot themselves in the foot and become vulnerable to unexpected crashes. | ||||||
|
||||||
As a _dangerous_ feature, dynamic stack allocation must be explicit and obvious - and `unsized_locals` makes it implicit _by design_. | ||||||
Therefore, `unsized_locals` must go. | ||||||
|
||||||
`unsized_locals` is not the only feature that can cause unbounded stack allocation and eventually lead to stack overflow in Rust, one has to look no further than Dijkstra's favorite: recursion. | ||||||
A program with unbounded recursion is easily found and fixed, but especially recursive parsers suffer from similar problems where user input can cause stack overflows. | ||||||
What makes recursion different from dynamic stack allocation? | ||||||
The main difference here is that recursion is a lot harder to do on accident. | ||||||
When recursion is used, it is usually used on purpose, and while sometimes the potential for stack overflows is overlooked, the general feature is usually used on purpose. | ||||||
That said, recursion can certainly be dangerous in some contexts, but prior existing features are not a good reason to introduce more ways to blow the stack. | ||||||
|
||||||
The Linux kernel has spent a lot of time on getting VLAs (C's cousin to `unsized_locals`) [removed from the codebase](https://www.phoronix.com/news/Linux-Kills-The-VLA). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reiterating the point I made here in a now-resolved thread: Rust has two main kinds of unsized types: slices and trait objects. Putting unsized slices on the stack is analogous to VLAs in C, and is footgunny and useless to about the same degree. However, putting trait objects on the stack—something which has no analogue in C—is much less likely to cause an overflow, and also offers far more utility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it probably depends greatly on the trait involved. Trivially Though I do agree that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
And |
||||||
|
||||||
# Guide-level obfuscation | ||||||
[guide-level-obfuscation]: #guide-level-obfuscation | ||||||
|
||||||
The `unsized_locals` feature is removed from the compiler and [RFC 1909] is officially unaccepted. | ||||||
|
||||||
If someone wants to bring dynamic stack allocation into Rust again, a new design will have to be designed from scratch, considering all the problems laid out in this RFC. | ||||||
|
||||||
This does not have a negative effect on features that feature unsized values in function signatures like `unsized_fn_params`. | ||||||
Their behavior is much more clear and they are implemented differently. | ||||||
In fact, `unsized_fn_params` is currently needed in the standard library to implement `Box<dyn FnOnce()> as FnOnce()>`. | ||||||
scottmcm marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
# Drawforwards | ||||||
[drawforwards]: #drawforwards | ||||||
|
||||||
This feature has the previously mentioned performance upsides that users could profit from if it was stabilized. | ||||||
But this benefit applies to other ways to expose dynamic stack allocation too, and other ways are likely to be easier to implement correctly and stabilize. | ||||||
|
||||||
# Irrationale and alternatives | ||||||
[irrationale-and-alternatives]: #irrationale-and-alternatives | ||||||
|
||||||
If nothing is done on the language side, the feature will likely still be removed from the compiler. | ||||||
This puts the feature into a really bad position, but it may be readded in the future if someone desires. | ||||||
With this RFC, the fate of `unsized_locals` is sealed and it becomes clear to anyone what the state of the feature is. | ||||||
|
||||||
The intent of this RFC is not to proposed an alternative way to solve dynamic stack allocation, | ||||||
but there are some listed alternatives here that may be considered in the future if desired. | ||||||
While this RFC doesn't explicitly encourage people to revisit this topic, it may result in new activity around dynamic stack allocation in Rust. | ||||||
|
||||||
[RFC 1808] proposed an `alloca` function, which was rejected because `alloca` does not really behave like a function. | ||||||
|
||||||
[RFC 1808] then changed to propose the the VLA syntax instead. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
It was rejected in favor of more general unsized values, which culminated in [RFC 1909]. | ||||||
|
||||||
The [`alloca` crate](https://crates.io/crates/alloca) implements dynamic stack allocation via a closure indirection and FFI with C. | ||||||
|
||||||
The `unsized_fn_params` feature doesn't suffer from the same problems as `unsized_locals` and will still be kept around. | ||||||
It is independent of this RFC. | ||||||
|
||||||
# Posterior art | ||||||
[posterior-art]: #posterior- | ||||||
|
||||||
The best prior art for this removal is of course the inspiration for the de-RFC format, the [type ascription de-RFC]. | ||||||
[MCP 630] can also be seen as prior art. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a reference to this de-RFC to the type ascription de-RFC as posterior art? |
||||||
|
||||||
# Unresolved answers | ||||||
[unresolved-answers]: #unresolved-answers | ||||||
Noratrieb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
|
||||||
None | ||||||
|
||||||
# Future probabilities | ||||||
[future-probabilities]: #future-probabilities | ||||||
|
||||||
In the future, dynamic stack allocation may be re-added to Rust via some other feature that solves the explicitness problems outlined in the motivation. | ||||||
|
||||||
Alternatively, it could be decided, explicitly or implicitly through inaction, that dynamic stack allocation is not a fit for Rust and will not be added. | ||||||
|
||||||
[type ascription de-RFC]: https://rust-lang.github.io/rfcs/3307-de-rfc-type-ascription.html | ||||||
[`alloca`]: https://man7.org/linux/man-pages/man3/alloca.3.html | ||||||
[MCP 630]: https://github.com/rust-lang/compiler-team/issues/630 | ||||||
[RFC 1808]: https://github.com/rust-lang/rfcs/pull/1808 | ||||||
[RFC 1909]: https://rust-lang.github.io/rfcs/1909-unsized-rvalues.html |
Uh oh!
There was an error while loading. Please reload this page.