- 
                Notifications
    
You must be signed in to change notification settings  - Fork 13.9k
 
Temporary lifetime extension for blocks #146098
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
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
78995b7    to
    2e55e56      
    Compare
  
    | 
           @rustbot label -stable-nominated I'm not intending to stable-nominate this, at least. Someone else can, but I don't expect it's needed or that it would be accepted.  | 
    
      
        
              This comment was marked as off-topic.
        
        
      
    
  This comment was marked as off-topic.
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
2e55e56    to
    eafdca9      
    Compare
  
    | 
           Does this only affect code in Rust 2024, or would you expect any visible difference in earlier editions?  | 
    
| 
           
 Suppose we have a macro  Or to generalize this, the aim of this PR is that in a non-extending context,  If new expressions are added to Rust that are both extending and temporary scopes, I'd want this behavior to apply to them as well.  | 
    
| 
           Since this would effectively reduce the scope of the Rust 2024 tail expression temporary scope change, we'd also want to be sure to reflect that in the behavior of the   | 
    
| 
           I haven't done extensive testing, but see this test diff for that lint: lint-tail-expr-drop-order-borrowck.rs. I'm applying the lifetime extension rules on all editions, and lifetime extension prevents the temporary scope from being registered as potentially forwards-incompatible (even though the extended scopes are technically the same as the old scopes in old editions). Though I think I've convinced myself at this point that lifetime extension doesn't need to be applied to block tails of non-extending old-edition blocks1, so potentially the lint change could be implemented in some other way instead. Footnotes
  | 
    
| 
           ☔ The latest upstream changes (presumably #146666) made this pull request unmergeable. Please resolve the merge conflicts.  | 
    
eafdca9    to
    e649bbe      
    Compare
  
    | 
           I've made some revisions. This should now properly handle  I think the implementation will likely need optimization and cleanup, but it might take a bit of refactoring to get it to a good place, so I'd like to get a vibe check on the design first, if there's room for it in a lang team meeting. @rustbot label +I-lang-nominated  | 
    
| 
           r? @nnethercote rustbot has assigned @nnethercote. Use   | 
    
…r=jackh726 [beta-1.91] Warn on future errors from temporary lifetimes shortening in Rust 1.92 Pursuant to [discussion on Zulip](https://rust-lang.zulipchat.com/#narrow/channel/474880-t-compiler.2Fbackports/topic/.23145838.3A.20beta-nominated/near/540530631), this implements a future-compatibility warning lint `macro_extended_temporary_scopes` for errors in Rust 1.92 caused by #145838: ``` warning: temporary lifetime shortening in Rust 1.92 --> $DIR/macro-extended-temporary-scopes.rs:54:14 | LL | &struct_temp().field | ^^^^^^^^^^^^^ this expression creates a temporary value... ... LL | } else { | - ...which will be dropped at the end of this block in Rust 1.92 | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see <https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#macro-extended-temporary-scopes> = note: consider using a `let` binding to create a longer lived value ``` Implementation-wise, this reuses the existing temporary scoping FCW machinery introduced for the `tail_expr_drop_order` edition lint: this adds `BackwardIncompatibleDropHint` statements to the MIR at the end of the shortened scopes for affected temporaries; these are then checked in borrowck to warn if the temporary is used after the future drop hint. There are trade-offs here: on one hand, I believe this gives some assurance over ad-hoc pattern-recognition that there are no false positives[^1]. On the other hand, this fails to lint on future dangling raw pointers and it complicates the potential addition of explanatory diagnostics or suggestions[^2]. I'm hopeful that the limitation around dangling pointers won't be relevant in real code, though; the only real instance we've seen of breakage so far is future errors in formatting macro invocations, which this should be able to catch. Release logistics notes: - This PR targets the beta branch directly, since the breakage it's a FCW for is landing in the next Rust version. - #146098 undoes the breakage this is a FCW for. If that behavior is merged and stabilizes in Rust 1.92, this PR should be reverted (or shouldn't be merged) in order to avoid spurious warnings. cc `@traviscross` `@rustbot` label +T-lang [^1]: In particular, more syntactic approaches are complicated by having to avoid warning on promoted constants; they'd either be full of holes, they'd need a lot of extra logic, or they'd need to hack more MIR-to-HIR mapping into `PromoteTemps`. [^2]: It's definitely possible to add more context and a suggestion, but the ways I've thought of to do so are either too hacky or too complex to feel appropriate for a last-minute direct-to-beta lint.
| 
           The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon.  | 
    
Co-authored-by: Travis Cross <[email protected]>
This is the real-world way that the unintentional stabilization of lifetime extension for format arguments was used.
e0252c2    to
    e41c711      
    Compare
  
    | 
           This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.  | 
    
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've rebased to re-bless the mir-opt tests. I couldn't tell at a glance why this wasn't the case before, but the changes here now apply to the panic-abort diff as well as the panic-unwind diff for the overloaded indexing mir-opt test. More details in the inline review below.
| StorageDead(_12); | ||
| StorageDead(_8); | ||
| StorageDead(_12); | 
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 appears to be inlined from
rust/library/core/src/slice/index.rs
Lines 432 to 439 in 82ae0ee
| if let Some(new_len) = usize::checked_sub(self.end, self.start) | |
| && self.end <= slice.len() | |
| { | |
| // SAFETY: `self` is checked to be valid and in bounds above. | |
| unsafe { &*get_offset_len_noubcheck(slice, self.start, new_len) } | |
| } else { | |
| slice_index_fail(self.start, self.end, slice.len()) | |
| } | 
where previously the storage for the slice pointer returned by
get_offset_len_noubcheck was dropped before the result of the bounds check1 and now it's dropped after, since it now lives past the execution of the if expression.
Footnotes
- 
See here and here for why that bool lives until then: individual boolean conditions have their scopes overridden so they live to the end of the
ifexpression. I have to assume this is so they can each can have a singleStorageDead, rather than one in each branch following each condition's switch terminator. ↩ 
| 
           @rustbot author I think I forgot to handle another sort of temporary scope in this (match arm scopes). Rather than special-casing each kind of temporary scope that can be extended through, I'm increasingly inclined to believe a more general solution (closer to how the Reference PR is written) will be better in the compiler too. Upwards traversal is messier, but it'll be less error-prone than special-casing.  | 
    
| 
           Reminder, once the PR becomes ready for a review, use   | 
    
| 
           Given that I've been picking up the reviews for the other work in this area, I guess I'm likely as qualified as anyone (which, is a very short list). So, that being said, I'll just steal this review. I'll watch for when this gets switched to waiting on review. r? jackh726  | 
    
| 
           r? BoxyUwU feel free to review it too if you like but I've been getting on with this over the past few weeks so would like to finish that out :)  | 
    
| 
           Sounds good!  | 
    
This implements a revised version of the temporary lifetime extension semantics I suggested in #145838 (comment), with the goal of making temporary lifetimes and drop order more consistent between extending and non-extending blocks. As a consequence, this undoes the breaking change introduced by #145838 (but in exchange has a much larger surface area).
The change this PR hopes to enforce is a general rule: any expression's temporaries should have the same relative drop order regardless of whether the expression is in an extending context or not:
let _ = $expr;anddrop($expr);should have the same drop order. To achieve that, this PR applies lifetime extension rules to blocks:now extends the lifetime of
temp()to outlive the block tail in Rust 2024 regardless of whether the block is an extending expression in aletstatement initializer (in which context it was already extended to outlive the block before this PR). The scoping rules for tails of extending blocks remain the same: extending subexpressions' temporary scopes are extended based on the source of the lifetime extension (e.g. to match the scope of a parentletstatement's bindings). For blocks not extended by any other source, extending borrows in the tail expression now share a temporary scope with the result of the block. This can in turn extend nested blocks within blocks' tail expressions:Since this uses the same rules as
let, it only applies to extending sub-expressions.This also applies to
ifexpressions' blocks since lifetime extension applies toifblocks' tail expressions, meaning it affects all editions. This is where breakage from #145838 was observed:now extends
temp()to have the same temporary scope as the result of theifexpression.As a further consequence, this makes
super letinifexpressions' blocks more consistent with block expressions:previously only worked in extending contexts (since the
super lets would be extended), and now it works everywhere.Reference PR: rust-lang/reference#2051
@rustbot label +T-lang