From 84e33e1173f9dd903050820627ab5cde008de68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20L=C3=B6bel?= Date: Fri, 18 Dec 2015 20:23:01 +0100 Subject: [PATCH 1/4] Add Rvalue-static-promotion RFC --- text/0000-rvalue_static_promotion.md | 171 +++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 text/0000-rvalue_static_promotion.md diff --git a/text/0000-rvalue_static_promotion.md b/text/0000-rvalue_static_promotion.md new file mode 100644 index 00000000000..420b83324fe --- /dev/null +++ b/text/0000-rvalue_static_promotion.md @@ -0,0 +1,171 @@ +- Feature Name: rvalue_static_promotion +- Start Date: 2015-12-18 +- RFC PR: +- Rust Issue: + +# Summary +[summary]: #summary + +Promote constexpr rvalues to values in static memory instead of +stack slots, and expose those in the language by being able to directly create +`'static` references to them. This would allow code like +`let x: &'static u32 = &42` to work. + +# Motivation +[motivation]: #motivation + +Right now, when dealing with constant values, you have to explicitly define +`const` or `static` items to create references with `'static` lifetime, +which can be unnecessarily verbose if those items never get exposed +in the actual API: + +```rust +fn return_x_or_a_default(x: Option<&u32>) -> &u32 { + if let Some(x) = x { + x + } else { + static DEFAULT_X: u32 = 42; + &DEFAULT_X + } +} +fn return_binop() -> &'static Fn(u32, u32) -> u32 { + const STATIC_TRAIT_OBJECT: &'static Fn(u32, u32) -> u32 + = &|x, y| x + y; + STATIC_TRAIT_OBJECT +} +``` + +Additionally, despite it being memory safe, it is not currently possible to +create a `&'static mut` to a zero-sized type without involving unsafe code: + +```rust +fn return_fn_mut_or_default(&mut self) -> &FnMut(u32, u32) -> u32 { + // error: references in constants may only refer to immutable values + const STATIC_TRAIT_OBJECT: &'static mut FnMut(u32, u32) -> u32 + = &mut |x, y| x * y; + + self.operator.unwrap_or(STATIC_TRAIT_OBJECT) +} +``` + +Lastly, the compiler already special cases a small subset of rvalue +const expressions to have static lifetime - namely the empty array expression: + +```rust +let x: &'static [u8] = &[]; +let y: &'static mut [u8] = &mut []; +``` + +And though they don't have to be seen as such, string literals could be regarded +as the same kind of special sugar: + +```rust +let b: &'static [u8; 4] = b"test"; +// could be seen as `= &[116, 101, 115, 116]` + +let s: &'static str = "foo"; +// could be seen as `= &str([102, 111, 111])` +// given `struct str([u8]);` and the ability to construct compound +// DST structs directly +``` + +With the proposed change, those special cases would instead become +part of a general language feature usable for custom code. + +# Detailed design +[design]: #detailed-design + +Inside a function body's block: + +- If a shared reference to a constexpr rvalue is taken. (`&`) +- And the constexpr does not contain a `UnsafeCell { ... }` constructor. +- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`. +- Then instead of translating the value into a stack slot, translate + it into a static memory location and give the resulting reference a + `'static` lifetime. + +Likewise, + +- If a mutable reference to a constexpr rvalue is taken. (`&mut `) +- And the constexpr does not contain a `UnsafeCell { ... }` constructor. +- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`. +- _And the type of the rvalue is zero-sized._ +- Then instead of translating the value into a stack slot, translate + it into a static memory location and give the resulting reference a + `'static` lifetime. + +The `UnsafeCell` restrictions are there to ensure that the promoted value is +truly immutable behind the reference (Though not technically needed in the zero-sized case, see alternatives below). + +The zero-sized restriction for mutable references is there because +aliasing mutable references are only safe for zero sized types +(since you never dereference the pointer for them). + +Examples: + +```rust +// OK: +let a: &'static u32 = &32; +let b: &'static Option> = &None; +let c: &'static Fn() -> u32 = &|| 42; + +let d: &'static mut () = &mut (); +let e: &'static mut Fn() -> u32 = &mut || 42; + +// BAD: +let f: &'static Option> = &Some(UnsafeCell { data: 32 }); +let g: &'static Cell = &Cell::new(); // assuming conf fn new() +``` + +These rules above should be consistent with the existing rvalue promotions in `const` +initializer lists: + +```rust +// If this compiles: +const X: &'static T = &; + +// Then this should compile as well: +let x: &'static T = &; +``` + +## Implementation + +The necessary changes in the compiler did already get implemented as +part of codegen optimizations (emitting references-to or memcopies-from values in static memory instead of embedding them in the code). + +All that is left do do is "throw the switch" for the new lifetime semantic +by removing these lines: +https://github.com/rust-lang/rust/blob/29ea4eef9fa6e36f40bc1f31eb1e56bf5941ee72/src/librustc/middle/mem_categorization.rs#L801-L807 + +(And of course fixing any fallout/bitrot that might have happened, adding tests, etc.) + +# Drawbacks +[drawbacks]: #drawbacks + +One more feature with seemingly ad-hoc rules to complicate the language... + +# Alternatives +[alternatives]: #alternatives + +There are two ways this could be taken further with zero-sized types: + +1. Remove the `UnsafeCell` restriction if the type of the rvalue is zero-sized. +2. The above, but also remove the __constexpr__ restriction, applying to any zero-sized rvalue instead. + +Both cases would work because one can't cause memory unsafety with a reference +to a zero sized value, and they would allow more safe code to compile. + +However, they might complicated reasoning about the rules more, +especially with the last one also being possibly confusing in regards to +side-effects. + +Not doing this mostly means relying on `static` and `const` items to create +`'static` references, while empty-array expressions would remain special cased. +It would also not be possible to safely create `&'static mut` references to zero-sized +types, though that part could also be achieved by allowing mutable references to +zero-sized types in constants. + +# Unresolved questions +[unresolved]: #unresolved-questions + +None, beyond "Should we do alternative 1 instead?". From c2ebeaf2017f8fef64844d4f773561ea8e3b122e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20L=C3=B6bel?= Date: Fri, 18 Dec 2015 20:59:27 +0100 Subject: [PATCH 2/4] Add generic example --- text/0000-rvalue_static_promotion.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/text/0000-rvalue_static_promotion.md b/text/0000-rvalue_static_promotion.md index 420b83324fe..bce3a11f09d 100644 --- a/text/0000-rvalue_static_promotion.md +++ b/text/0000-rvalue_static_promotion.md @@ -35,6 +35,16 @@ fn return_binop() -> &'static Fn(u32, u32) -> u32 { } ``` +This workaround also has the limitation of not being able to refer to +type parameters of a containing generic functions, eg you can't do this: + +```rust +fn generic() -> &'static Option { + const X: &'static Option = &None::; + X +} +``` + Additionally, despite it being memory safe, it is not currently possible to create a `&'static mut` to a zero-sized type without involving unsafe code: @@ -112,6 +122,12 @@ let c: &'static Fn() -> u32 = &|| 42; let d: &'static mut () = &mut (); let e: &'static mut Fn() -> u32 = &mut || 42; +let h: &'static u32 = &(32 + 64); + +fn generic() -> &'static Option { + &None:: +} + // BAD: let f: &'static Option> = &Some(UnsafeCell { data: 32 }); let g: &'static Cell = &Cell::new(); // assuming conf fn new() From 1d9f54175c62cf9fcee6e7dea00dcf06121779aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20L=C3=B6bel?= Date: Fri, 18 Dec 2015 21:04:45 +0100 Subject: [PATCH 3/4] Add Drawback elobaration to the generic case --- text/0000-rvalue_static_promotion.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/0000-rvalue_static_promotion.md b/text/0000-rvalue_static_promotion.md index bce3a11f09d..cf250758fcf 100644 --- a/text/0000-rvalue_static_promotion.md +++ b/text/0000-rvalue_static_promotion.md @@ -175,9 +175,11 @@ However, they might complicated reasoning about the rules more, especially with the last one also being possibly confusing in regards to side-effects. -Not doing this mostly means relying on `static` and `const` items to create -`'static` references, while empty-array expressions would remain special cased. -It would also not be possible to safely create `&'static mut` references to zero-sized +Not doing this means: + +- Relying on `static` and `const` items to create `'static` references, which won't work in generics. +- Empty-array expressions would remain special cased. +- It would also not be possible to safely create `&'static mut` references to zero-sized types, though that part could also be achieved by allowing mutable references to zero-sized types in constants. From db2444065181515e75384d47fc657cb70682b395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20L=C3=B6bel?= Date: Sat, 12 Nov 2016 21:11:30 +0100 Subject: [PATCH 4/4] Move mutable rvalue promotion to alternative section --- text/0000-rvalue_static_promotion.md | 79 ++++++++++++++++------------ 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/text/0000-rvalue_static_promotion.md b/text/0000-rvalue_static_promotion.md index cf250758fcf..039e3906979 100644 --- a/text/0000-rvalue_static_promotion.md +++ b/text/0000-rvalue_static_promotion.md @@ -45,25 +45,11 @@ fn generic() -> &'static Option { } ``` -Additionally, despite it being memory safe, it is not currently possible to -create a `&'static mut` to a zero-sized type without involving unsafe code: - -```rust -fn return_fn_mut_or_default(&mut self) -> &FnMut(u32, u32) -> u32 { - // error: references in constants may only refer to immutable values - const STATIC_TRAIT_OBJECT: &'static mut FnMut(u32, u32) -> u32 - = &mut |x, y| x * y; - - self.operator.unwrap_or(STATIC_TRAIT_OBJECT) -} -``` - -Lastly, the compiler already special cases a small subset of rvalue +However, the compiler already special cases a small subset of rvalue const expressions to have static lifetime - namely the empty array expression: ```rust let x: &'static [u8] = &[]; -let y: &'static mut [u8] = &mut []; ``` And though they don't have to be seen as such, string literals could be regarded @@ -94,22 +80,8 @@ Inside a function body's block: it into a static memory location and give the resulting reference a `'static` lifetime. -Likewise, - -- If a mutable reference to a constexpr rvalue is taken. (`&mut `) -- And the constexpr does not contain a `UnsafeCell { ... }` constructor. -- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`. -- _And the type of the rvalue is zero-sized._ -- Then instead of translating the value into a stack slot, translate - it into a static memory location and give the resulting reference a - `'static` lifetime. - The `UnsafeCell` restrictions are there to ensure that the promoted value is -truly immutable behind the reference (Though not technically needed in the zero-sized case, see alternatives below). - -The zero-sized restriction for mutable references is there because -aliasing mutable references are only safe for zero sized types -(since you never dereference the pointer for them). +truly immutable behind the reference. Examples: @@ -119,9 +91,6 @@ let a: &'static u32 = &32; let b: &'static Option> = &None; let c: &'static Fn() -> u32 = &|| 42; -let d: &'static mut () = &mut (); -let e: &'static mut Fn() -> u32 = &mut || 42; - let h: &'static u32 = &(32 + 64); fn generic() -> &'static Option { @@ -134,7 +103,7 @@ let g: &'static Cell = &Cell::new(); // assuming conf fn new() ``` These rules above should be consistent with the existing rvalue promotions in `const` -initializer lists: +initializer expressions: ```rust // If this compiles: @@ -160,9 +129,49 @@ https://github.com/rust-lang/rust/blob/29ea4eef9fa6e36f40bc1f31eb1e56bf5941ee72/ One more feature with seemingly ad-hoc rules to complicate the language... -# Alternatives +# Alternatives, Extensions [alternatives]: #alternatives +It would be possible to extend support to `&'static mut` references, +as long as there is the additional constraint that the +referenced type is zero sized. + +This again has precedence in the array reference constructor: + +```rust +// valid code today +let y: &'static mut [u8] = &mut []; +``` + +The rules would be similar: + +- If a mutable reference to a constexpr rvalue is taken. (`&mut `) +- And the constexpr does not contain a `UnsafeCell { ... }` constructor. +- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`. +- _And the type of the rvalue is zero-sized._ +- Then instead of translating the value into a stack slot, translate + it into a static memory location and give the resulting reference a + `'static` lifetime. + +The zero-sized restriction is there because +aliasing mutable references are only safe for zero sized types +(since you never dereference the pointer for them). + +Example: + +```rust +fn return_fn_mut_or_default(&mut self) -> &FnMut(u32, u32) -> u32 { + self.operator.unwrap_or(&mut |x, y| x * y) + // ^ would be okay, since it would be translated like this: + // const STATIC_TRAIT_OBJECT: &'static mut FnMut(u32, u32) -> u32 + // = &mut |x, y| x * y; + // self.operator.unwrap_or(STATIC_TRAIT_OBJECT) +} + +let d: &'static mut () = &mut (); +let e: &'static mut Fn() -> u32 = &mut || 42; +``` + There are two ways this could be taken further with zero-sized types: 1. Remove the `UnsafeCell` restriction if the type of the rvalue is zero-sized.