-
Notifications
You must be signed in to change notification settings - Fork 535
const-eval.const-expr.borrows: mention indirect places #1865
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
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 |
---|---|---|
|
@@ -81,11 +81,127 @@ r[const-eval.const-expr.builtin-arith-logic] | |
operators used on integer and floating point types, `bool`, and `char`. | ||
|
||
r[const-eval.const-expr.borrows] | ||
* All forms of [borrow]s, including raw borrows, with one limitation: | ||
mutable borrows and shared borrows to values with interior mutability | ||
are only allowed to refer to *transient* places or to *static* places. A place is *transient* | ||
if its lifetime is strictly contained inside the current [const context]. | ||
A place is *static* if it is a `static` item or a [promoted expression]. | ||
* All forms of [borrow]s, including raw borrows, except: | ||
|
||
* Mutable borrows of expressions whose temporary scopes would be extended (see [temporary lifetime extension]) to the end of the program. | ||
* Shared borrows of expressions whose temporary scopes would be extended to the end of the program when those expressions result in values with interior mutability. | ||
|
||
```rust,compile_fail,E0764 | ||
// Due to being in tail position, this borrow extends the scope of the | ||
// temporary to the end of the program. Since the borrow is mutable, | ||
// this is not allowed in a const expression. | ||
const C: &u8 = &mut 0; // ERROR not allowed | ||
``` | ||
|
||
```rust,compile_fail,E0764 | ||
// Const blocks are similar to initializers of `const` items. | ||
let _: &u8 = const { &mut 0 }; // ERROR not allowed | ||
``` | ||
|
||
```rust,compile_fail,E0492 | ||
# use core::sync::atomic::AtomicU8; | ||
// This is not allowed as 1) the temporary scope is extended to the | ||
// end of the program and 2) the temporary has interior mutability. | ||
const C: &AtomicU8 = &AtomicU8::new(0); // ERROR not allowed | ||
``` | ||
RalfJung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust,compile_fail,E0492 | ||
# use core::sync::atomic::AtomicU8; | ||
// As above. | ||
let _: &_ = const { &AtomicU8::new(0) }; // ERROR not allowed | ||
``` | ||
|
||
```rust | ||
# #![allow(static_mut_refs)] | ||
// Even though this borrow is mutable, it's not of a temporary, so | ||
// this is allowed. | ||
const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK | ||
``` | ||
|
||
```rust | ||
# use core::sync::atomic::AtomicU8; | ||
// Even though this borrow is of a value with interior mutability, | ||
// it's not of a temporary, so this is allowed. | ||
const C: &AtomicU8 = { | ||
static S: AtomicU8 = AtomicU8::new(0); &S // OK | ||
}; | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
```rust | ||
# use core::sync::atomic::AtomicU8; | ||
// This shared borrow of an interior mutable temporary is allowed | ||
// because its scope is not extended. | ||
const C: () = { _ = &AtomicU8::new(0); }; // OK | ||
``` | ||
|
||
```rust | ||
// Even though the borrow is mutable and the temporary lives to the | ||
// end of the program due to promotion, this is allowed because the | ||
// borrow is not in tail position and so the scope of the temporary | ||
// is not extended via temporary lifetime extension. | ||
const C: () = { let _: &'static mut [u8] = &mut []; }; // OK | ||
// ~~ | ||
// Promoted temporary. | ||
``` | ||
|
||
> [!NOTE] | ||
> In other words, shared borrows of interior mutable data and mutable borrows are only allowed in a const context when the borrowed place expression is *transient*, *indirect*, or *static*. | ||
> | ||
> A place expression is *transient* if it is a variable local to the current [const context] or an expression whose temporary scope is contained inside the current const context. | ||
Comment on lines
+148
to
+150
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. Is this note really worth 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 don't know. I'll try it on someone who hasn't read this yet. 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. On the one hand, approaching it from this direction felt more necessary before adding the examples in the main section. With that explication, this doesn't carry so much weight. On the other hand, one purpose of the Reference is to document common terms for things, and writing down and naming the three kinds of place expressions does seem useful. It could be that this is in the wrong place in the document and it'd be better elsewhere. 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.
(emphasis mine) The thing I am wondering is if these are truly common terms. If they were, they'd be defined elsewhere and then just references here. Right now they look more like ad-hoc artifacts of const-check than common terms. 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. Interesting... stepped on a homonym there. By "common", I meant "shared" rather than "frequently observed" (i.e. in the sense of "tragedy of the commons"). In the sense I meant it, putting it in the Reference (and people's use of the Reference for this purpose) makes it common. |
||
> | ||
> ```rust | ||
> // The borrow is of a variable local to the initializer, therefore | ||
> // this place expresssion is transient. | ||
> const C: () = { let mut x = 0; _ = &mut x; }; | ||
> ``` | ||
> | ||
> ```rust | ||
> // The borrow is of a temporary whose scope has not been extended, | ||
> // therefore this place expression is transient. | ||
> const C: () = { _ = &mut 0u8; }; | ||
> ``` | ||
> | ||
> ```rust | ||
> // When a temporary is promoted but not lifetime extended, its | ||
> // place expression is still treated as transient. | ||
> const C: () = { let _: &'static mut [u8] = &mut []; }; | ||
> ``` | ||
> | ||
> A place expression is *indirect* if it is a [dereference expression]. | ||
> | ||
> ```rust | ||
> const C: () = { _ = &mut *(&mut 0); }; | ||
> ``` | ||
> | ||
> A place expression is *static* if it is a `static` item. | ||
> | ||
> ```rust | ||
> # #![allow(static_mut_refs)] | ||
> const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; | ||
> ``` | ||
|
||
> [!NOTE] | ||
> One surprising consequence of these rules is that we allow this, | ||
> | ||
> ```rust | ||
> const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // OK | ||
> // ~~~~~~~ | ||
> // Empty arrays are promoted even behind mutable borrows. | ||
> ``` | ||
> | ||
> but we disallow this similar code: | ||
> | ||
> ```rust,compile_fail,E0764 | ||
> const C: &[u8] = &mut []; // ERROR | ||
> // ~~~~~~~ | ||
> // Tail expression. | ||
> ``` | ||
> | ||
> The difference between these is that, in the first, the empty array is [promoted] but its scope does not undergo [temporary lifetime extension], so we consider the place expression to be transient (even though after promotion the place indeed lives to the end of the program). In the second, the scope of the empty array temporary does undergo lifetime extension, and so it is rejected due to being a mutable borrow of a lifetime-extended temporary (and therefore borrowing a non-transient place expression). | ||
> | ||
> The effect is surprising because temporary lifetime extension, in this case, causes less code to compile than would without it. | ||
> | ||
> See [issue #143129](https://github.com/rust-lang/rust/issues/143129) for more details. | ||
|
||
r[const-eval.const-expr.deref] | ||
* The [dereference operator] except for raw pointers. | ||
|
@@ -175,6 +291,7 @@ of whether you are building on a `64` bit or a `32` bit system. | |
[const generic parameters]: items/generics.md#const-generics | ||
[constants]: items/constant-items.md | ||
[Const parameters]: items/generics.md | ||
[dereference expression]: expressions/operator-expr.md#the-dereference-operator | ||
[dereference operator]: expressions/operator-expr.md#the-dereference-operator | ||
[destructors]: destructors.md | ||
[enum discriminants]: items/enumerations.md#discriminants | ||
|
@@ -197,9 +314,11 @@ of whether you are building on a `64` bit or a `32` bit system. | |
[paths]: expressions/path-expr.md | ||
[patterns]: patterns.md | ||
[promoted expression]: destructors.md#constant-promotion | ||
[promoted]: destructors.md#constant-promotion | ||
[range expressions]: expressions/range-expr.md | ||
[slice]: types/slice.md | ||
[statics]: items/static-items.md | ||
[struct]: expressions/struct-expr.md | ||
[temporary lifetime extension]: destructors.scope.lifetime-extension | ||
[tuple expressions]: expressions/tuple-expr.md | ||
[while]: expressions/loop-expr.md#predicate-loops |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'd prefer to avoid the redundancy here and spell this out a bit differently. For instance, we could say something like
But this is just editorial so 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pros and cons. Will think on this.