Surprising lifetime inference with closures
The following code fails to compile due to a lifetime error: the intent is for the closure to mutably borrow the variable only until it returns, but somehow the lifetime of the borrow gets inferred to last as long as the variable itself.
fn main() {
let func = |a| { a; };
//let func = |a: &mut u32| { a; };
//let func = |a| { };
let mut what: u32 = 2;
loop {
func(&mut what);
}
}
a.rs:7:19: 7:23 error: `what` does not live long enough
a.rs:7 func(&mut what);
^~~~
a.rs:2:27: 9:2 note: reference must be valid for the block suffix following statement 0 at 2:26...
a.rs:2 let func = |a| { a; };
a.rs:3 //let func = |a| { };
a.rs:4 //let func = |a: &mut u32| { a; };
a.rs:5 let mut what: u32 = 2;
a.rs:6 loop {
a.rs:7 func(&mut what);
...
a.rs:5:27: 9:2 note: ...but borrowed value is only valid for the block suffix following statement 1 at 5:26
a.rs:5 let mut what: u32 = 2;
a.rs:6 loop {
a.rs:7 func(&mut what);
a.rs:8 }
a.rs:9 }
Either of the commented alternatives makes compilation succeed. The first, making the lifetime signature (semi-)explicit, makes sense; it's odd that simply removing a useless reference to the variable also works, though.
Anyway, while I don't know how the lifetime inference algorithm works, and in particular I don't know whether improving the situation could impact backwards compatibility, it's surprising to me that it can't figure out a reasonable lifetime in this case.
I think the closure is inferred to be
FnOnce.mitaa at 2016-03-02 00:02:49
I just ran the example, and it appears that this is still a bug?
$ rustc --version rustc 1.23.0 (766bd11c8 2018-01-01) $ cargo --version cargo 0.24.0 (45043115c 2017-12-05)// src/main.rs fn main() { let func = |a| { a; }; let mut what: u32 = 2; loop { func(&mut what); } }error[E0597]: `what` does not live long enough --> src/main.rs:7:1 | 5 | func(&mut what); | ---- borrow occurs here 6 | } 7 | } | ^ `what` dropped here while still borrowed | = note: values in a scope are dropped in the opposite order they are createdgeemili at 2018-01-27 14:04:42
Hello all,
first off, thanks for your efforts in this language! I've just started with Rust and really like it. Cargo and the strong compiler are awesome! :)
This issue looks quite similar to the following behaviour.
Code snippet
1 fn main() { 2 println!("start"); 3 4 let data = vec![ 5 String::from("Hello"), 6 String::from("world"), 7 String::from("!"), 8 ]; 9 10 // let filter = |_: &_| true; // okay 11 let filter = |_| true; // warning/error 12 13 for obj in data.into_iter().map(Some) { 14 // error since obj is swallowed by filter 15 // filter(obj); 16 // println!("{:?}", obj); // error occurs here 17 18 // okay since only borrowed to filter 19 filter(&obj); 20 println!("{:?}", obj); // okay 21 } 22 23 println!("finish"); 24 }Compiler answer
From Playground with
stable,edition 2018,optimization off.--- Standard Error --- Compiling playground v0.0.1 (/playground) warning[E0597]: `obj` does not live long enough --> src/main.rs:19:16 | 19 | filter(&obj); | ------ ^^^^ borrowed value does not live long enough | | | borrow later used here 20 | println!("{:?}", obj); // okay 21 | } | - `obj` dropped here while still borrowed | = warning: this error has been downgraded to a warning for backwards compatibility with previous releases = warning: this represents potential undefined behavior in your code and this warning will become a hard error in the future Finished dev [unoptimized + debuginfo] target(s) in 1.48s Running `target/debug/playground` --- Standard Output --- start Some("Hello") Some("world") Some("!") finishExpected
Since the closure is borrowing
objand finishes before line 21, I have expected that the value isn't borrowed in line 21 anymore. Arguing from the perspective of the|_|-closure, the closure could receive ownership in some cases (see line 15) and thus throws an error, restrictively, in every case without checking, which would fit to the seen behaviour.Since the moved
obj(not&obj) could be swallowed by the closure once, this is not a problem per se. On the other hand, I have recognized this lack in my code just because the compiler has told me.Parga Cacheiro, Dominic at 2019-06-11 19:01:48
Current output: (1.68 stable and 1.70 nightly, 2021 ed)
error[[E0499]](https://doc.rust-lang.org/stable/error_codes/E0499.html): cannot borrow `what` as mutable more than once at a time --> src/main.rs:7:14 | 7 | func(&mut what); | ---- ^^^^^^^^^ `what` was mutably borrowed here in the previous iteration of the loop | | | first borrow used here, in later iteration of loop For more information about this error, try `rustc --explain E0499`.Dylan DPC at 2023-03-10 11:53:15
Current output:
error[E0499]: cannot borrow `what` as mutable more than once at a time --> src/main.rs:7:14 | 7 | func(&mut what); | ---- ^^^^^^^^^ `what` was mutably borrowed here in the previous iteration of the loop | | | first borrow used here, in later iteration of looperror[E0597]: `obj` does not live long enough --> src/main.rs:19:16 | 13 | for obj in data.into_iter().map(Some) { | --- binding `obj` declared here ... 19 | filter(&obj); | ------ ^^^^ borrowed value does not live long enough | | | borrow later used here 20 | println!("{:?}", obj); // okay 21 | } | - `obj` dropped here while still borrowedAnother case from https://www.reddit.com/r/rust/comments/1clg8ij/strange_borrow_checking_error_if_type_annotation/:
fn main() { let c = |_| { }; { let x = 0; c(&x); } c(&0); }error[E0597]: `x` does not live long enough --> src/main.rs:6:11 | 5 | let x = 0; | - binding `x` declared here 6 | c(&x); | ^^ borrowed value does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | c(&0); | - borrow later used hereEsteban Kuber at 2024-05-06 19:25:13