-
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?
Conversation
9fe65cb
to
9d83c64
Compare
Based on the last commit I added in rust-lang/rust#140942, instead of a list of things we allow, we could consider just saying what is not allowed -- mutable / interior mutable borrows of lifetime-extended places. Maybe that would be better? |
Thanks @RalfJung; @ehuss and talked about this new language. On first pass, it looks pretty good to us. To your question about stating this in terms of what's not allowed, that also had some appeal to us, and we actually think it'd be best in this case to do both, i.e. to state what's allowed, as is done here, and to then also approach the same truth from the other side by stating what's not allowed. We do that in a number of places, and this seems like a good candidate for it, in terms of being as clear as possible. |
I've given that a shot, please let me know what you think. I also realized |
Thanks. Probably we'll want to add some examples here for each. Regarding the "top-level scope" bit, I wonder if there's a more clear way we could say that. We allow, of course, e.g.: struct S;
const fn temp() -> S { S }
const C1: &S = { let y = { let x = &temp(); x }; y };
const C2: &S = 'top: { let y = { let x = &temp(); break 'top x; }; }; That is, there's a distinction between the temporary being created in the top-level scope and being referenced by the final value. |
All of those are immutable references so yes of course we allow them. That doesn't really exercise the clauses this PR is about, though. There are no examples for any of the surrounding text here so I'll not try to figure out the best way to integrate examples into this part of the docs. |
The question is about this change specifically, which is independent of mutability: r[destructors.scope.lifetime-extension.static]
-Lifetime extension also applies to `static` and `const` items, where it makes temporaries live until the end of the program.
+Lifetime extension also applies to the top-level scope of `static` and `const` items, where it makes temporaries live until the end of the program. It seems that the rule for this should be essentially the same as the rule that precedes it, except that we're extending to
(Admittedly, that rule is rather ambiguous itself.) So if this "top-level scope" qualification makes sense for one, then it would seem to make sense for the other. But I think it gets into some ambiguity about "are we talking about the temporary being created in the top-level scope or about the temporary being referenced by the value returned from the top-level scope?". Clearly we don't mean the former. |
We mean "the temporary is extended to the scope outside the const initializer expression". Not sure what is the best way to put that... |
We'll take care of integrating these, but @ehuss and I would be interested if you could provide here, just as a reply, differentiating examples (i.e. examples that fit in exactly one category) for each of the things we're defining here, i.e. transient places, indirect places, places based on static items, and places based on promoted expressions. |
ae67917
to
2ee9fd4
Compare
Thanks. I pushed a clarification to define this in terms of the other rule that touches on this. |
src/destructors.md
Outdated
Lifetime extension also applies to the top-level scope of `static` and `const` items, | ||
where it makes temporaries live until the end of the program. | ||
For example: | ||
The temporary scopes for expressions in `const` and `static` item initializers are sometimes extended until the end of the program following the same rule as in [destructors.scope.lifetime-extension.let]. For example: |
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.
"sometimes" isn't very helpful, is it? Can't we say something more precise, such as what you say in the commit message?
In
const
andstatic
item initializers, if the scope of a temporary would be extended to a scope outside that of the initializer expression, then the temporary lives until the end of the program.
And then maybe we want to add something like this somewhere:
This effectively makes such a temporary a new global variable. This global variable is always immutable; the limitations in [const-eval.const-expr.borrows] rule out constants where a mutable temporary would have to live until the end of the program.
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.
In reading the rest of the section more carefully, I actually feel like we should just defer this to another PR. The existing language has to be read in the context of everything that follows it -- most of which is talking about let
statements even though it also applies to const
and static
items -- and in that context, all this is trying to say, when it says,
Lifetime extension also applies to
static
andconst
items, where it makes temporaries live until the end of the program.
is that the rules that precede and follow this rule, even though they're referring to let
statements, "also" apply to static
and const
items, modulo that in this case, rather than extending to the scope of the enclosing block, "it makes temporaries live until the end of the program."
Probably there's a way to improve this section overall, but I'm now convinced doing that coherently requires touching many of the rules here.
(I pulled the change to this rule out of the commit and dropped my commit.)
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.
rust-lang/rust#140942 now already introduced the "top-level" wording into compiler errors. That should then probably be adjusted if that is not the term we use in the reference. Any suggestion for what the error should say?
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.
I'm thinking about this. My first inclination is to say something to the effect of:
mutable borrow of a temporary whose lifetime would be extended to
'static
is not allowed in aconst
initializer
and:
shared borrow of an interior mutable temporary whose lifetime would be extended to
'static
is not allowed in aconst
initializer
(This is maybe a bit of a pun, because strictly speaking references have their lifetimes extended while temporaries have their scopes extended, and as phrased, it's not clear whether we're talking about the reference (due to the borrow) or the temporary, but it doesn't matter here, so it seems OK to me, and it seems probably more familiar here to talk about lifetime extension to 'static
rather than about scope extension to the end of the program.)
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.
That's very close to what I did in rust-lang/rust#143092, except I do refer to scopes there.
(Sorry, I should have mentioned here that I made a PR.)
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.
Ah, yes, that looks good too.
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.
...except I do refer to scopes there.
(Looking at the diff, it actually went with "lifetime extended until the end of the program".)
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.
Ah, right. So a mix of "scope extended" and "lifetime becomes 'static
", I guess.
static mut S: i32= 0;
#[allow(static_mut_refs)]
const C: () = {
let mut x = 0;
let mref = &mut x; // reference to transient place
let mref2 = &mut *mref; // reference to indirect place
let sref = unsafe { &mut S }; // reference to static place
let pref: &mut [i32] = &mut []; // reference to promoted
}; |
691ec18
to
846f675
Compare
Thanks @RalfJung for the examples. I've pushed commits to integrate those and some others and to hopefully clarify some language. Let me know if that all looks right. |
bf9bc37
to
07b3859
Compare
In testing, I notice this: ---- const_eval.md - Constant_evaluation::Constant_expressions (line 212) stdout ----
error[E0764]: mutable borrows of lifetime-extended temporaries in the top-level scope of a constant are not allowed
--> const_eval.md:213:18
|
3 | const C: &[u8] = &mut [];
| ^^^^^^^
error: aborting due to 1 previous error This borrow isn't of an interior mutable temporary. Does this imply a second or a broader exception, you think, or is this covered by something else? |
Thanks to RalfJ for the substance of many of these.
Rather than talking about lifetime-extended temporaries in the top-level scope of an initializer, which is maybe a bit ambiguous, let's speak directly to the result of the lifetime extension, which is that these temporaries disallowed for borrows (in cases of interior mutability) would have their lifetimes extended to the end of the program.
07b3859
to
c55401b
Compare
It's mutable, but not interior mutable. Though what I find odd is that |
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, with one limitation: mutable borrows and shared borrows of expressions whose temporary scope would be extended (see [temporary lifetime extension]) to the end of the program are not allowed when those borrows refer to values with interior mutability. |
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.
This makes it sound like the "when those borrows refer to values with interior mutability" part applies to everything, which isn't correct, it only applies to "shared borrows". Basically, borrows with any form of mutability are disallowed to refer to such temporaries, and we have two kinds of mutable borrows: actually mutable borrows, and shared borrows with interior mutability.
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.
Interesting. Prior to my commit, the language was:
All forms of [borrow]s, including raw borrows, with one limitation: mutable borrows and shared borrows to values with interior mutability are not allowed to refer to lifetime-extended temporaries in the top-level scope of a
const
orstatic
initializer expression.
I had read this same ambiguity into this verbiage. Thinking about it now, of course, this doesn't make any sense, as mutable references to UnsafeCell
aren't special. I'll clarify the verbiage.
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.
"mutable borrows and shared borrows to values with interior mutability" was intended to parse as
- mutable borrows, and
- shared borrows to values with interior mutability
English makes it hard to express this unambiguously...
```rust,compile_fail,E0492 | ||
# use core::sync::atomic::AtomicU8; | ||
let _: &'static _ = const { &AtomicU8::new(0) }; // ERROR not allowed | ||
``` |
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.
I can't figure out which of these examples is meant to demonstrate what.
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.
Fair enough. I'll add some comments.
``` | ||
|
||
```rust | ||
const C: &[u8] = &[]; // Borrow of promoted expression. |
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.
This isn't even in the class of "borrows with some form of mutability", so I don't understand how it makes sense as an example here.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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.
This needs to be reworked due to #1865 (comment) as well.
|
||
```rust,compile_fail,E0764 | ||
# use core::sync::atomic::AtomicU8; | ||
const C: &AtomicU8 = &mut AtomicU8::new(0); // ERROR not allowed |
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.
Mixing &mut
and interior mutability just causes confusion about which source of mutability actually matters here, I think we should avoid it in these examples.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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.
Ah, I see in the comment over in #1865 (comment) this does touch on an ambiguity in the text.
So, uh, looks like we are doing the static const checks before we do promotion? @oli-obk is that expected? That means we should remove promotion from the text and examples in this PR. |
static const checks happen before promotion intentionally. I think they should even be a part of typeck, and I think we should be able to move more of it into typeck |
Okay, looks like I just entirely forgot about that then. That's good, discussing promoteds here got a bit awkward IMO... |
… r=oli-obk const checks for lifetime-extended temporaries: avoid 'top-level scope' terminology This error recently got changed in rust-lang#140942 to use the terminology of "top-level scope", but after further discussion in rust-lang/reference#1865 it seems the reference will not be using that terminology after all. So let's also remove it from the compiler again, and let's focus on what actually happens with these temporaries: their lifetime is extended until the end of the program. r? `@oli-obk` `@traviscross`
… r=oli-obk const checks for lifetime-extended temporaries: avoid 'top-level scope' terminology This error recently got changed in rust-lang#140942 to use the terminology of "top-level scope", but after further discussion in rust-lang/reference#1865 it seems the reference will not be using that terminology after all. So let's also remove it from the compiler again, and let's focus on what actually happens with these temporaries: their lifetime is extended until the end of the program. r? ``@oli-obk`` ``@traviscross``
It seems a rather subtle point to document, given that: const C1: &[u8] = { let x: &'static mut [u8] = &mut []; x }; //~ OK
const C2: &[u8] = { &mut [] }; //~ ERROR |
Rollup merge of #143092 - RalfJung:const-check-lifetime-ext, r=oli-obk const checks for lifetime-extended temporaries: avoid 'top-level scope' terminology This error recently got changed in #140942 to use the terminology of "top-level scope", but after further discussion in rust-lang/reference#1865 it seems the reference will not be using that terminology after all. So let's also remove it from the compiler again, and let's focus on what actually happens with these temporaries: their lifetime is extended until the end of the program. r? ``@oli-obk`` ``@traviscross``
const checks for lifetime-extended temporaries: avoid 'top-level scope' terminology This error recently got changed in rust-lang/rust#140942 to use the terminology of "top-level scope", but after further discussion in rust-lang/reference#1865 it seems the reference will not be using that terminology after all. So let's also remove it from the compiler again, and let's focus on what actually happens with these temporaries: their lifetime is extended until the end of the program. r? ``@oli-obk`` ``@traviscross``
Yeah, that is kind of odd -- the fact that lifetime extension kicks in here means that code that would otherwise have compiled (if there was no lifetime extension) now does not compile. |
Follow-up to #1858.