Skip to content

do temporary lifetime extension in a single pass - #165229

Open
dianne wants to merge 3 commits into
rust-lang:mainfrom
dianne:single-pass-extension
Open

dianne wants to merge 3 commits into
rust-lang:mainfrom
dianne:single-pass-extension

Conversation

@dianne

@dianne dianne commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

rather than doing a separate traversal each time we encounter a point we can lifetime-extend through (i.e. a let statement initializer or a const/static body), this tracks the scope lifetime-extended temporaries should be extended to in a similar way to how it tracks whether subexpressions introduce temporary scopes.

this refactor is the basis for both #146098 and #160297 (the latter of which is part of rust-lang/goals#648), as it makes the things I'm doing there easier to implement. the additional tests are also pulled from #146098, since I expect we want to have them around regardless of that PR being blocked

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 31, 2026
@dianne

dianne commented Oct 31, 2026

Copy link
Copy Markdown
Member Author

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Oct 31, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Oct 31, 2026
do temporary lifetime extension in a single pass
@rust-bors

rust-bors Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: a18535c (a18535c32cc670e9affe18831a3eb1157e872cc3)
Base parent: 92b5b8c (92b5b8c87c61b20adc9001b41baf587634a74734)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (a18535c): comparison URL.

Overall result: ❌✅ regressions and improvements - please read:

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

Next, please: If you can, justify the regressions found in this try perf run in writing along with @rustbot label: +perf-regression-triaged. If not, fix the regressions and do another perf run. Neutral or positive results will clear the label automatically.

@bors rollup=iffy rustc-perf
@rustbot label: -S-waiting-on-perf +perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.2% [0.2%, 0.2%] 4
Improvements ✅
(primary)
-0.2% [-0.2%, -0.1%] 5
Improvements ✅
(secondary)
-0.2% [-0.2%, -0.2%] 2
All ❌✅ (primary) -0.2% [-0.2%, -0.1%] 5

Max RSS (memory usage)

Results (primary 2.3%, secondary 1.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.3% [2.3%, 2.3%] 1
Regressions ❌
(secondary)
2.6% [2.3%, 2.8%] 3
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-1.0% [-1.0%, -1.0%] 1
All ❌✅ (primary) 2.3% [2.3%, 2.3%] 1

Cycles

Results (primary 2.8%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.8% [2.8%, 2.8%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 2.8% [2.8%, 2.8%] 1

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 484.843s -> 488.027s (0.66%)
Artifact size: 406.61 MiB -> 406.78 MiB (0.04%)

@rustbot rustbot added perf-regression Performance regression. and removed S-waiting-on-perf Status: Waiting on a perf run to be completed. labels Oct 31, 2026
@tiif

tiif commented Nov 1, 2026

Copy link
Copy Markdown
Member

r? me I will at least do a first pass review on this.

@dianne

dianne commented Nov 4, 2026 •

Copy link
Copy Markdown
Member Author

the tt-muncher perf regressions seem like they're likely noise, or at least not directly related to what this PR is doing? locally profiling the one with the highest significance factor (check, incr-unchanged), ~97% of the icount diff looks to be in <rustc_expand::expand::MacroExpander>::fully_expand_fragment, which of course happens before temporary scoping (and is about where I'd expect the most icount variance, considering the benchmark)

maybe it'd be worth doing another try build and perf run sometime to see if it stops reporting regressions? I'll leave things as-is for now though

@dianne
dianne marked this pull request as ready for review November 4, 2026 14:23
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Nov 4, 2026

@tiif tiif left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome PR! I am generally trying to understand how temporary lifetime extension works, so here are some questions :3

View changes since this review

Comment on lines +425 to +426
hir::ExprKind::Cast(subexpr, _) => {
resolve_expr(visitor, subexpr, NodeInfo { drop_temps: false, extending: true });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why extending is set a s true for cast?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because a cast's operand is extending (or in the Reference's current terminology, if a cast expression is extending, its operand is also extending1)

this can be seen e.g. in

let x = &String::from("string") as &dyn Debug;
println!("{x:?}");

since the the String temporary is borrowed by a borrow operator, it can be lifetime-extended past the end of the let statement if it's in an extending position within its parent expression (and that's also in extending position, etc., until reaching the let statement's initializer expression)

note that the terminology I'm using here is a bit of a compromise between the current Reference text and the model #146098 uses2, since this PR sets things up for it

Footnotes

  1. https://doc.rust-lang.org/reference/destructors.html#r-destructors.scope.lifetime-extension.exprs ↩

  2. under that PR's model, a subexpression being extending means it preserves the scope of lifetime-extendable temporaries from its parent (this is what extending: true does). there's still a difference in how non-extending subexpressions are handled though: this PR keeps the current behavior of not lifetime-extending lifetime-extendable temporaries at all, but in Temporary lifetime extension for blocks #146098, non-extending subexpressions set the scope of lifetime-extendable temporaries to be the enclosing temporary scope at that point (i.e. the scope of a non-extended temporary at that point). this is relevant in cases like { &temp() }: if that's in an extending position within a let initializer, a const, or a static, temp() outlives the block. otherwise, it's currently dropped at the end of the block. under Temporary lifetime extension for blocks #146098, it always outlives the block (to whatever the scope of lifetime-extendable temporaries is at that point), as if it were scoped as a temporary of whatever super-expression is using it (up to a conservative syntactic approximation). this lets it actually be used, similar to how lifetime extension currently can give temporaries borrowed by let bindings the scopes of those bindings, since that's necessary in order to use the bindings ↩

Comment on lines 29 to 30
/// Region parent of expressions, etc.
parent: Option<Scope>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does region parent of expressions mean?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's outdated terminology I imagine, probably from before MIR borrowck? in the context of that specific module, it's roughly the parent scope as defined in the Reference1. it differs a little in how it marks temporary scope boundaries. for HIR nodes that drop their temporaries at the end, it adds a ScopeData::Destruction "scope" above the scope for the HIR node to mark it. more nuanced temporary scope boundaries are instead marked with a ScopeData::IfThenRescope or ScopeData::MatchGuard scope. see ScopeTree::default_temporary_scope for how that works

Footnotes

  1. https://doc.rust-lang.org/reference/destructors.html#r-destructors.scope.nesting ↩

Comment on lines +585 to 600
let prev_extended_parent = visitor.cx.extended_parent;

if let_kind == LetKind::Regular {
// When visiting the initializer, extend borrows and `super let`s accessible through
// extending subexpressions to live in the current variable scope (or in the case of
// statics and consts, for the whole program).
visitor.cx.extended_parent = Some(ExtendedScope(visitor.cx.var_parent));
}
}

// Make sure we visit the initializer first.
// The correct order, as shared between drop_ranges and intravisitor,
// is to walk initializer, followed by pattern bindings, finally followed by the `else` block.
if let Some(expr) = init {
visitor.visit_expr(expr);
// Make sure we visit the initializer first.
// The correct order, as shared between drop_ranges and intravisitor, is
// to walk initializer, followed by pattern bindings, finally followed by the `else` block.
resolve_expr(visitor, expr, NodeInfo { drop_temps: false, extending: true });

visitor.cx.extended_parent = prev_extended_parent;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find myself being confused about what extended_parent and var_parent are after reading this part 🤔 Do you have an example for this?

@dianne dianne Nov 9, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var_parent is the scope used for variables. e.g. variables declared by a normal let statement live to the end of the block

extended_parent is the scope of lifetime-extendable temporaries (used by the operands of borrow expressions and super let bindings). a name more tailored to borrow operators might be better, especially since there's a project goal to move away from super let statements. the idea of tracking a scope for lifetime-extendable temporaries is key to #146098 (which I get into in #165229 (comment))

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

perf-regression Performance regression. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants