Specify temporary lifetime extension through expressions by dianne · Pull Request #2051 · rust-lang/reference (original) (raw)

@dianne, you said this would require inventing more notation than an explicit stack -- why that? Other than picking a symbol for the hole, what notation do you need?

The remaining bit of notation I was thinking of is for concatenation/substitution/composition of contexts, in order to talk about a local context (which we examine) within a larger context (which we assign a name). e.g., for determining the temporary scope of a borrow expression's operand, the object we're determining the temporary scope of is &●-within-some-larger-context. I assume this is a standard operation (especially as concerns going from "an expression with a hole" to "the expression with the hole filled in"), so it's likely there's some standard notation I'm not aware of. My first guesses would be some sort of brackets to evoke application, ∘ for composition, or ⧺ for concatenation.

My rather naive first impression is that the main thing we have to define is the grammar of permitted contexts -- that doesn't even need inference rules, it just needs to say which "expressions with a hole" we can put around an &_ and have it be lifetime extended to the outside of that context:

Defining a grammar of permitted contexts works, but the trick is that the scope of lifetime-extended temporaries depends on the context in which the lifetime extension context appears, so I'd like a way to capture that formally as well (hence reaching for composition). For stable Rust, we can be somewhat ad-hoc about this: if the extending context is the initializer of a let statement, we extend to the block the let statement is in; if the extending context is a const/static body, we extend to outside of the program's runtime. For the new lifetime extension semantics I'm proposing though, under this framing, I'd like for a lifetime extension context to be able to start in any non-lifetime-extension context, in which case it extends temporaries to the temporary scope enclosing the non-lifetime-extension context. This is so, e.g., the popular patterns

print!("{}", if cond() { &format!(...) } else { "..." });

and

print!("{}", if cond() { format_args!(...) } else { format_args!(...) });

work. Currently, since the &format!(...) and format_args!(...) are not in extending contexts, their temporaries are dropped within the if expression's block; they would however be extended if the if expression was directly made the initializer of a let statement. Under my proposed semantics, the &format!(...) and format_args!(...) would be in extending contexts that end at the argument to print!, so they'd be dropped in the temporary scope enclosing that context: the statement (a yet larger context).

Another way to view my proposal, which reflects the intuition I'd propose to capture in the Reference, is that temporary scope of the operand of a borrow operator is always determined by lifetime extension rules: you remove the longest extending suffix from its context before determining its temporary scope. Under this view, formally, I'm imagining lifetime extension would just be part of the definition of "the temporary scope of a context", though for pedagogical reasons they should maybe still be presented separately in the Reference.

I'm not entirely sure what a "scope" would be formalized as, I just don't know lifetime extension well enough.

I'd say it could be viewed as a syntactic context as well, but with some semantics bolted on (when execution leaves the context, values associated with it are dropped). For the most part scopes do correspond to expressions' contexts, but you'd probably need more notation, e.g. to represent scopes that don't correspond directly to single AST objects, such as "The pattern-matching condition(s) and consequent body of if"; that one can't clearly be written as an expression with a hole, even though formally it's not that different.