Skip to content

Commit 50d603d

Browse files
committed
RFC 1414 rvalue static promotion
Merge branch 'rvalue_static_promotion' of https://github.com/Kimundi/rfcs
2 parents 9ccd885 + db24440 commit 50d603d

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

text/1414-rvalue_static_promotion.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
- Feature Name: rvalue_static_promotion
2+
- Start Date: 2015-12-18
3+
- RFC PR: [#1414](https://github.com/rust-lang/rfcs/pull/1414)
4+
- Rust Issue: [#38865](https://github.com/rust-lang/rust/issues/38865)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Promote constexpr rvalues to values in static memory instead of
10+
stack slots, and expose those in the language by being able to directly create
11+
`'static` references to them. This would allow code like
12+
`let x: &'static u32 = &42` to work.
13+
14+
# Motivation
15+
[motivation]: #motivation
16+
17+
Right now, when dealing with constant values, you have to explicitly define
18+
`const` or `static` items to create references with `'static` lifetime,
19+
which can be unnecessarily verbose if those items never get exposed
20+
in the actual API:
21+
22+
```rust
23+
fn return_x_or_a_default(x: Option<&u32>) -> &u32 {
24+
if let Some(x) = x {
25+
x
26+
} else {
27+
static DEFAULT_X: u32 = 42;
28+
&DEFAULT_X
29+
}
30+
}
31+
fn return_binop() -> &'static Fn(u32, u32) -> u32 {
32+
const STATIC_TRAIT_OBJECT: &'static Fn(u32, u32) -> u32
33+
= &|x, y| x + y;
34+
STATIC_TRAIT_OBJECT
35+
}
36+
```
37+
38+
This workaround also has the limitation of not being able to refer to
39+
type parameters of a containing generic functions, eg you can't do this:
40+
41+
```rust
42+
fn generic<T>() -> &'static Option<T> {
43+
const X: &'static Option<T> = &None::<T>;
44+
X
45+
}
46+
```
47+
48+
However, the compiler already special cases a small subset of rvalue
49+
const expressions to have static lifetime - namely the empty array expression:
50+
51+
```rust
52+
let x: &'static [u8] = &[];
53+
```
54+
55+
And though they don't have to be seen as such, string literals could be regarded
56+
as the same kind of special sugar:
57+
58+
```rust
59+
let b: &'static [u8; 4] = b"test";
60+
// could be seen as `= &[116, 101, 115, 116]`
61+
62+
let s: &'static str = "foo";
63+
// could be seen as `= &str([102, 111, 111])`
64+
// given `struct str([u8]);` and the ability to construct compound
65+
// DST structs directly
66+
```
67+
68+
With the proposed change, those special cases would instead become
69+
part of a general language feature usable for custom code.
70+
71+
# Detailed design
72+
[design]: #detailed-design
73+
74+
Inside a function body's block:
75+
76+
- If a shared reference to a constexpr rvalue is taken. (`&<constexpr>`)
77+
- And the constexpr does not contain a `UnsafeCell { ... }` constructor.
78+
- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`.
79+
- Then instead of translating the value into a stack slot, translate
80+
it into a static memory location and give the resulting reference a
81+
`'static` lifetime.
82+
83+
The `UnsafeCell` restrictions are there to ensure that the promoted value is
84+
truly immutable behind the reference.
85+
86+
Examples:
87+
88+
```rust
89+
// OK:
90+
let a: &'static u32 = &32;
91+
let b: &'static Option<UnsafeCell<u32>> = &None;
92+
let c: &'static Fn() -> u32 = &|| 42;
93+
94+
let h: &'static u32 = &(32 + 64);
95+
96+
fn generic<T>() -> &'static Option<T> {
97+
&None::<T>
98+
}
99+
100+
// BAD:
101+
let f: &'static Option<UnsafeCell<u32>> = &Some(UnsafeCell { data: 32 });
102+
let g: &'static Cell<u32> = &Cell::new(); // assuming conf fn new()
103+
```
104+
105+
These rules above should be consistent with the existing rvalue promotions in `const`
106+
initializer expressions:
107+
108+
```rust
109+
// If this compiles:
110+
const X: &'static T = &<constexpr foo>;
111+
112+
// Then this should compile as well:
113+
let x: &'static T = &<constexpr foo>;
114+
```
115+
116+
## Implementation
117+
118+
The necessary changes in the compiler did already get implemented as
119+
part of codegen optimizations (emitting references-to or memcopies-from values in static memory instead of embedding them in the code).
120+
121+
All that is left do do is "throw the switch" for the new lifetime semantic
122+
by removing these lines:
123+
https://github.com/rust-lang/rust/blob/29ea4eef9fa6e36f40bc1f31eb1e56bf5941ee72/src/librustc/middle/mem_categorization.rs#L801-L807
124+
125+
(And of course fixing any fallout/bitrot that might have happened, adding tests, etc.)
126+
127+
# Drawbacks
128+
[drawbacks]: #drawbacks
129+
130+
One more feature with seemingly ad-hoc rules to complicate the language...
131+
132+
# Alternatives, Extensions
133+
[alternatives]: #alternatives
134+
135+
It would be possible to extend support to `&'static mut` references,
136+
as long as there is the additional constraint that the
137+
referenced type is zero sized.
138+
139+
This again has precedence in the array reference constructor:
140+
141+
```rust
142+
// valid code today
143+
let y: &'static mut [u8] = &mut [];
144+
```
145+
146+
The rules would be similar:
147+
148+
- If a mutable reference to a constexpr rvalue is taken. (`&mut <constexpr>`)
149+
- And the constexpr does not contain a `UnsafeCell { ... }` constructor.
150+
- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`.
151+
- _And the type of the rvalue is zero-sized._
152+
- Then instead of translating the value into a stack slot, translate
153+
it into a static memory location and give the resulting reference a
154+
`'static` lifetime.
155+
156+
The zero-sized restriction is there because
157+
aliasing mutable references are only safe for zero sized types
158+
(since you never dereference the pointer for them).
159+
160+
Example:
161+
162+
```rust
163+
fn return_fn_mut_or_default(&mut self) -> &FnMut(u32, u32) -> u32 {
164+
self.operator.unwrap_or(&mut |x, y| x * y)
165+
// ^ would be okay, since it would be translated like this:
166+
// const STATIC_TRAIT_OBJECT: &'static mut FnMut(u32, u32) -> u32
167+
// = &mut |x, y| x * y;
168+
// self.operator.unwrap_or(STATIC_TRAIT_OBJECT)
169+
}
170+
171+
let d: &'static mut () = &mut ();
172+
let e: &'static mut Fn() -> u32 = &mut || 42;
173+
```
174+
175+
There are two ways this could be taken further with zero-sized types:
176+
177+
1. Remove the `UnsafeCell` restriction if the type of the rvalue is zero-sized.
178+
2. The above, but also remove the __constexpr__ restriction, applying to any zero-sized rvalue instead.
179+
180+
Both cases would work because one can't cause memory unsafety with a reference
181+
to a zero sized value, and they would allow more safe code to compile.
182+
183+
However, they might complicated reasoning about the rules more,
184+
especially with the last one also being possibly confusing in regards to
185+
side-effects.
186+
187+
Not doing this means:
188+
189+
- Relying on `static` and `const` items to create `'static` references, which won't work in generics.
190+
- Empty-array expressions would remain special cased.
191+
- It would also not be possible to safely create `&'static mut` references to zero-sized
192+
types, though that part could also be achieved by allowing mutable references to
193+
zero-sized types in constants.
194+
195+
# Unresolved questions
196+
[unresolved]: #unresolved-questions
197+
198+
None, beyond "Should we do alternative 1 instead?".

0 commit comments

Comments
 (0)