Description
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.
- A checkmark (✅) is used for cases where either 1. code is now accepted, or 2. code is (still) rejected but has a clear diagnostic explaining why.
- The column marked 2018 is testing here the 2018 edition, where migration mode gets most of the benefits from NLL. They will also eventually trickle down to the 2015 edition when we switch that to use NLL as well.
- The magnifying glass (🔍) is for cases where we issue a diagnostic suggesting that one add a local binding or a semi-colon (depending on whether the value is needed or not), explaining that the temporaries (under the current language semantics) are being dropped too late without it. This sort of case is, to my eye, acceptable.
- The column marked NLL is for cases where you need to opt into
#![feature(nll)]
(or use the synonymous command line options-Z borrowck=mir -Z two-phase-borrows
) to get the nicest output. In most cases the 2018 output is the same, so I didn't fill in the column in those cases.
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<Owner> = 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.)