Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Temporaries and Lifetime Extension

A “value-to-place coercion” occurs when a value expression is used in a context where a place is needed, e.g. because it is borrowed, matched on, or has a field accessed. Whenever that happens, the value will get stored in a temporary variable. In this step, we make these temporaries explicit.

The rules that determine the scope of these temporaries are complex; they’re described in [destructors.scope.temporary]. You may also enjoy this blog post with a more explanatory style.

In this step, for each expression $expr to be coerced, we first add a let tmp; statement, then assign it tmp = $expr; (these two steps can sometimes be merged), then use tmp where the expression was. The placement of the let tmp; determines how long the value will live and its drop order. To get the right scope, extra blocks { .. } may be added.

For example:

#![allow(unused)]
fn main() {
let s = if Option::is_some(&Option::clone(&opt)) {
    let _x = &42;
    &String::new()
} else {
    &String::new()
};

// becomes:
let tmp3;
let tmp4;
let s = if { let tmp1 = Option::clone(&opt); Option::is_some(&tmp1) } {
    let tmp2 = 42;
    let _x = &tmp2;
    tmp3 = String::new();
    &tmp3
} else {
    tmp4 = String::new();
    &tmp4
};
}

Or:

#![allow(unused)]
fn main() {
let opt: RwLock<Option<u32>> = ...
if let Some(x) = Option::as_ref(&*Result::unwrap(RwLock::read(&opt))) {
    ...
} else {
    ...
}

// becomes (in edition 2024):
if let tmp = Result::unwrap(RwLock::read(&opt)) && let Some(x) = Option::as_ref(&*tmp) {
    ...
} else {
    ...
}
}

Note how in let chains we may introduce the temporaries as part of the let chain to get the right scope. Our Extended Let Chains allow forward declarations let x; in the middle of a let chain for that purpose.

Taking an example from the edition book:

#![allow(unused)]
fn main() {
fn f() -> usize {
    let c = RefCell::new("..");
    c.borrow().len()
}

// Becomes, after method resolution:
fn f() -> usize {
    let c = RefCell::new("..");
    str::len(*<Ref<_> as Deref>::deref(&RefCell::borrow(&c)))
}

// Before 2024, this becomes:
fn f() -> usize {
    let tmp1; // Added at the start of scope so that it drops after the other locals.
    let tmp2;
    let c = RefCell::new("..");
    tmp1 = RefCell::borrow(&c); // error[E0597]: `c` does not live long enough
    tmp2 = <Ref<_> as Deref>::deref(&tmp1);
    str::len(*tmp2)
}

// After 2024, this becomes:
fn f() -> usize {
    let c = RefCell::new("..");
    let tmp1; // drops before other locals
    let tmp2;
    tmp1 = RefCell::borrow(&c);
    tmp2 = <Ref<_> as Deref>::deref(&tmp1);
    str::len(*tmp2)
}
}

There is an exception to the above: temporaries can, when sensible [destructors.scope.const-promotion], become statics instead of local variables. This is called “constant promotion”:

#![allow(unused)]
fn main() {
let x = &1 + 2;

// becomes:
static TMP: u32 = 1 + 2;
let x = &TMP; // this allows `x` to have type `&'static u32`
}

After this step, all place contexts contain place expressions.

The rest of this section is a work-in-progress experiment about making the book executable.


pub fn desugar_value_to_place(program: &mut Program) -> Result<(), CompilationError> {
    program.visit_all_mut(|_block: &mut BlockExpression| {
        // TODO: look for all the `VirtualExpression::ValueToPlaceCoercion`
        Ok(())
    })
}