new scoping rules for safe dtors can yield spurious semi-colon or trailing unit expr · Issue #21114 · rust-lang/rust (original) (raw)

Spawned off of #21022, #21972

There are a number of situations where a trailing expression like a for-loop ends up being treated by the region lifetime inferencer as requiring a much longer lifetime assignment than what you would intuitively expect.

A lot of the relevant cases have been addressed. In fact, its gotten to the point that I want to have a summary comment up here that specifies the status for each example. Each demo should link to a playpen (using AST-borrowck when that has a checkmark, and NLL when AST-borrowck does not have a checkmark). Some rows also include a link to the comment where it was pointed out.

Example on playpen 2015 2018 NLL source
io::stdin demo 1
io::stdin demo 2 🤢
alloc::rc demo
ebfull drain demo 🤢 #21114 (comment)
felispere stdio demo 🤢 #21114 (comment)
drbawb demo #21114 (comment)
niconii demo 🤢 🔍 #21114 (comment)
kixunil demo 🤢 #21114 (comment)
thiez demo 1 🤢 #21114 (comment)
thiez demo 2 🤢 🔍 #21114 (comment)
shepmaster demo 🤢 #21114 (comment)
jonas-schievink demo 🤢 🤢 #42574
stephaneyfx demo 1 🤢 🔍 #46413 (comment)
stephaneyfx demo 2 🤢 🔍 #46413 (comment)

More details for issue follow.

A simple example of this (from examples embedded in the docs for io::stdio):

use std::io;

let mut stdin = io::stdin();
for line in stdin.lock().lines() {
    println!("{}", line.unwrap());
};

Another example, this time from an example for alloc::rc (where this time I took the time to add a comment explaining what I encountered):

fn main() { let gadget_owner : Rc = Rc::new( Owner { name: "Gadget Man".to_string(), gadgets: RefCell::new(Vec::new()) } );

let gadget1 = Rc::new(Gadget{id: 1, owner: gadget_owner.clone()});
let gadget2 = Rc::new(Gadget{id: 2, owner: gadget_owner.clone()});

gadget_owner.gadgets.borrow_mut().push(gadget1.clone().downgrade());
gadget_owner.gadgets.borrow_mut().push(gadget2.clone().downgrade());

for gadget_opt in gadget_owner.gadgets.borrow().iter() {
    let gadget = gadget_opt.upgrade().unwrap();
    println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}

// This is an unfortunate wart that is a side-effect of the implmentation
// of new destructor semantics: if the above for-loop is the final expression
// in the function, the borrow-checker treats the gadget_owner as needing to
// live past the destruction scope of the function (which of course it does not).
// To work around this, for now I am inserting a dummy value just so the above
// for-loop is no longer the final expression in the block.
()

}

Luckily for #21022, the coincidence of factors here is not very frequent, which is why I'm not planning on blocking #21022 on resolving this, but instead leaving it as something to address after issues for the alpha have been addressed.

(I'm filing this bug before landing the PR so that I can annotation each of the corresponding cases with a FIXME so that I can go back and address them after I get a chance to investigate this properly.)