Repository navigation
Compile error if lifetime parameter is not bounded by itself #88884
Description
Activity
I was able to find a smaller minimal example (here's the playground):
fn evil_iter<'a>(arg: &'a Vec<&'a str>) -> impl Iterator<Item=&'a &'a str> { arg.iter() } fn first_str_len<'z: 'z>(arg: Vec<&'z str>) -> usize { let first = evil_iter(&arg).next().unwrap(); let return_arg = |str: &'z str| str; return_arg(first).len() }I think the compiler error is correct, i.e.
argis not being borrowed long enough forfirstto be a&'z &'z str, but the fact that doing'z : 'zfixes the error message is the bug.@rustbot claim
UPDATE: Found a smaller minimal example (here's the playground):
fn ident<'a>(arg: &'a &'a ()) -> &'a &'a () { arg } fn do_nothing<'z: 'z>(arg: &'z ()) { (|s: &'z ()| s)(ident(&arg)); }Thank you for finding a smaller minimal example.
I tried based on this and I have a different opinion. My hypothesis is that it is a bug that it does not compile. It is possible that I am not understanding something.
| 3 | fn do_nothing<'z>(arg: &'z ()) { | -- lifetime `'z` defined here 4 | (|s: &'z ()| s)(ident(&arg)); | ------^^^^- | | | | | borrowed value does not live long enough | argument requires that `arg` is borrowed for `'z` 5 | } | - `arg` dropped here while still borrowedThe compiler says
`arg` dropped here while still borrowedat the end of the function, but in my opinion, the result of(|s: &'z ()| s)(ident(&arg))should be dropped before that.The next one isn't about
'z: 'z. Here is a functions with the same name but different lifetime parameters. In my opinion, all these functions need to be able to compile without errors.// will not compile fn ident<'a>(arg: &'a &'a ()) -> &'a &'a () { arg } // will compile // two different lifetime parameters fn ident<'a, 'b>(arg: &'a &'b ()) -> &'a &'b () { arg } // will not compile // Return a reference with the shorter of the two lifetime parameters (if my understanding of lifetimes is correct) fn ident<'a, 'b: 'a>(arg: &'a &'b ()) -> &'a &'a () { arg } fn do_nothing<'z>(arg: &'z ()) { (|s: &'z ()| s)(*ident(&arg)); }
Again, it's possible that I'm not understanding something.
I think you may be misunderstanding the borrow checker and the effect the
|s: &'z ()| shas. The closure|s: &'z ()| sis the identity function, but it has a type annotation on its argument which has a notable effect: it requires the argument to this function to be&'z (). Therefore, the shared reference passed into this function must live for at least the lifetime'z. Otherwise, it will not satisfy the argument type of this closure.Ergo, for
do_nothingto typecheck,ident(&arg)must be a&&'z (), i.e. it is a shared reference to the shared reference. However,identspecifically says the return type is&'a &'a ()for some lifetime'a. Notice how both lifetimes are the same, i.e. both the shared reference itself and the borrow of the shared reference must live for the lifetime'a. Since we are requiring the shared reference to live for'z, this means the borrow of the shared reference also lives for'z, i.e.ident(&arg)is a&'z &'z (). Ergo, sinceidentis just the identity function, this means&argmust be a&'z &'z (). Ergo, our borrow of the shared referenceargmust live for the whole lifetime'z. However,&argis a borrow created inside this function, which means that borrow must necessarily be dropped at the end of the body ofdo_nothing. Since'zis a lifetime parameter ofdo_nothing,'zoutlives the body ofdo_nothingbecause it comes from outside the call todo_nothing. Ergo, our borrow&arginside the body ofdo_nothingcan not possibly live for the whole lifetime'z. That is why the borrow checker says "argdropped here while still borrowed": Our type annotations requires the borrow&argto live for the whole lifetime'z, but the borrow gets dropped at the end of the function while the lifetime'zis still active.The other function
fn ident<'a, 'b: 'a>(arg: &'a &'b ()) -> &'a &'a () { arg }has the same problem because it also returns&'a &'a (). Since you are requiring'b: 'a, it is true that'ais a shorter or equal lifetime to'b, but in the end, our type annotation on the closure|s : 'z ()| swill still requireident(&arg)to be a&&'z (), so since the return type of this function is&'a &'a (), that meansident(&arg)must be a&'z &'z (), which causes the same problem caused above.Reacted by trrkI didn't understand the code correctly. I think I understand it now thanks to your explanation. Incidentally, I also didn't understand that references with an unacceptable lifetime, such as
&'z &'z ()generated in do_nothing, cannot even be generated, regardless of whether they are retained after generation or not.By the way, I have a new discovery. This code may have the same problem as
'z: 'z.fn ident<'a>(arg: &'a &'a ()) -> &'a &'a () { arg } fn do_nothing<'z: 'y, 'y>(arg: &'z ()) { (|s: &'y ()| s)(ident(&arg)); }
It can be compiled. However, I think it should be a compile error because the lifetime
'ycannot live outside the function. This is only true if my understanding is correct that the lifetime defined in the function signature must live outside the function, regardless of whether it is written as an argument type or not. I believe this is the case because the following code does not compile.fn ident<'a>(arg: &'a &'a ()) -> &'a &'a () { arg } fn do_nothing<'z: 'y, 'y>(arg: &'z ()) { ident(&arg) as &'y &'y (); }
the lifetime defined in the function signature must live outside the function, regardless of whether it is written as an argument type or not
My understanding is that you are correct: Any lifetime parameter of a function must outlive the body of that function.
Also, I agree that your new example should not compile, it has the same problem as the original example.
Reacted by trrkMaybe this issue should be closed.
We concluded that 'z: 'z should also cause an error, but it compiles without error, and that this is a bug.
We have some code that should compile error if there is no bug. These cause errors when compiled with the current nightly build. This is probably because #98835 has been merged.version: 1.65.0-nightly (2022-08-06 2befdef)
I used Rust Playground to check the behavior.
Stable version: 1.62.1
Nightly version: 1.65.0-nightly (2022-08-06 2befdef)This code can be compiled in stable and cannot be compiled in nightly.
Code that should compile error if there is no bug
fn ident<'a>(arg: &'a &'a ()) -> &'a &'a () { arg } fn do_nothing<'z: 'z>(arg: &'z ()) { (|s: &'z ()| s)(ident(&arg)); }
Copied from #88884 (comment)
fn evil_iter<'a>(arg: &'a Vec<&'a str>) -> impl Iterator<Item=&'a &'a str> { arg.iter() } fn first_str_len<'z: 'z>(arg: Vec<&'z str>) -> usize { let first = evil_iter(&arg).next().unwrap(); let return_arg = |str: &'z str| str; return_arg(first).len() }
Copied from #88884 (comment)
fn ident<'a>(arg: &'a &'a ()) -> &'a &'a () { arg } fn do_nothing<'z: 'y, 'y>(arg: &'z ()) { (|s: &'y ()| s)(ident(&arg)); }
Copied from #88884 (comment)
fn main() { let wrapped_vec = WrappedVec { vec: vec!["1"], }; let first = first_str_len(wrapped_vec); println!("first: {}", first); } struct WrappedVec<'a> { vec: Vec<&'a str>, } impl<'a> WrappedVec<'a> { fn iter(&'a self) -> impl Iterator<Item=&'a &'a str> { self.vec.iter() } } fn first_str_len<'a: 'a>(arg: WrappedVec<'a>) -> usize { let first = arg.iter().next().unwrap(); let return_arg = |str: &'a str| -> &'a str { str }; return_arg(first).len() }
from #88884 (comment)
With rust 1.64.0, the expected behavior occurs, i.e. compile errors. So I will close this as resolved.
The compiler behaves strangely. For a given lifetime, if it bounds itself (
'a: 'a), it will compile, otherwise it will not. I think it is a bug that this subtle difference can change whether it can be compiled or not.I tried this code:
This code does not compile.
You can compile it by rewriting the part shown below.
The code is unnatural because it was rebuilt by extracting only the parts that seemed necessary for replication from the code where the problem was found.
Meta
rustc --version --verbose:I tried it in the Rust playground, and the same thing happened in beta and nightly.
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=05a374855198719d56d41ac02be0d59e
Backtrace
There was no backtrace