changed 4 years ago
Linked with GitHub

async live values over yield

  • #72651 ICE: Broken MIR
  • #72956 Indexing via index method and [idx] sugar works differently in async blocks/functions
  • #69663 More precise generator captures
    • #57017 async/await: awaiting inside a match block captures borrow too eagerly

The relevant code

impl Foo { fn bar(&self) { } }

fn foo() {
   let x = ...;
   let y = ...;
   
   let z = foo().bar();
   
   // desugared
   let z = { let f = foo(); Foo::bar(&f) };
   
   yield;
   
}
  • Basic idea:
    • Each time we encounter a variable binding, check if there is a yield in the scope of that variable, and if so, record it as "potentially live across a yield" (here)
      • Approximate because:
        • the variable may not have a destructor and may not be used after the yield ({ let p = &X; ... await; } where X is not Send)
        • the variable may be moved and hence not live after the yield #57478
    • Each time we see an expression, assume it may get stored in a temporary, and check if that temporary would be live across a yield (here)
      • Approximate but it's hard to say exactly when
        • not all expressions make temporaries, but the expression's value may be used when an await occurs if there is an incomplete expression (see example below)
          • foo(bar, x.await, baz), (bar, x.await, baz)
          • temporary:
            • bar().as_ref() or &bar()
        • source of #57017, where the match client.status() introduces an auto-ref on client but since this is a place expression ("lvalue") no temporary is created and the value is ultimately not live across the await (if this had been Client::new().status() the story would be different)
        • source of #72956, where peach[0] in let r = &peach[0] winds up having an extended temporary lifetime until the end of the block, but in fact no temporary will be created

Example where treating just as temporaries would be wrong:

foo(X, bar.await, Y)
let foo = (X, bar.await, Y);

Example ergonomic win that would derive from treating variables with more precision:

let x = mutex.lock();
*x += 1;
drop(x);
foo.await

Temporary rules sidebar

let x = &Client::new();
// let x = Client::new();
// let x = &x;

let x = (&Client::new(), &Client::new());
let x = Foo { f: &Client::new() };

let x = Client::new().as_ref(); // RFC #66 would fix this =) 

relevant reference doc

ExprUseVisitor

  • code that does analysis for closures
  • link

other liveness like analyses

When do we care

fn foo() -> SomeStruct { }
match foo().bar {
    x => { }
} // <-- remainder of `foo()` is freed here
Select a repo