Lifetimes on &mut self are very very rarely legitimately useful, but can easily lead to even worse gotcha: when you mix them with traits or make them dependent on other lifetimes with a bigger scope, they can end up meaning the call exclusively borrows the object for its entire existence (you can call one method once, and nothing else with it ever).
Good point, and thank you for the example of where it would end up causing another confusing error!
In this case I put on an explicit lifetime just to make all of them explicit, but you’re right that it’s not legitimately useful here. It would probably be better on the method, if I want to leave it for explicitness.
(For what it’s worth, I also didn’t have that parameter in the code this blog post was inspired by.)
…they can end up meaning the call exclusively borrows the object for its entire existence (you can call one method once, and nothing else with it ever).
What would be an example of this, out of curiosity?
struct Bad<'a>(&'a str);
impl<'a> Bad<'a> {
fn gameover(&'a mut self) {}
}
fn main() {
let mut b = Bad("don't put references in structs");
b.gameover();
b.gameover(); // error: cannot borrow `b` as mutable more than once at a time
}
Lifetime elision in functions is really the biggest anti feature in rust. Instead of making things simple it just makes lifetimes much more confusing especially for beginners.
This is obviously a very subjective take, but I would nonetheless say that:
This specifically has to do with lifetime elision in paths, which are even less explicit because there are no ‘&’
IDE such as VS Code with inlay hints really enhance the development experience in that regard (especially if you use a shortcut to hide/display the hints imo)
I don’t recommend ever putting lifetime annotations on
&mut self
. In this case it’s sufficient to only nameToken
’s lifetime:Lifetimes on
&mut self
are very very rarely legitimately useful, but can easily lead to even worse gotcha: when you mix them with traits or make them dependent on other lifetimes with a bigger scope, they can end up meaning the call exclusively borrows the object for its entire existence (you can call one method once, and nothing else with it ever).Good point, and thank you for the example of where it would end up causing another confusing error!
In this case I put on an explicit lifetime just to make all of them explicit, but you’re right that it’s not legitimately useful here. It would probably be better on the method, if I want to leave it for explicitness.
(For what it’s worth, I also didn’t have that parameter in the code this blog post was inspired by.)
What would be an example of this, out of curiosity?
I shot myself in the foot with the same gun while also writing a parser a few weeks ago, but there is in fact a lint for this, which one should arguably add to any new code base. https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#elided-lifetimes-in-paths
Ooh thanks for this, I’ll look to add it!
Lifetime elision in functions is really the biggest anti feature in rust. Instead of making things simple it just makes lifetimes much more confusing especially for beginners.
This is obviously a very subjective take, but I would nonetheless say that:
Another solution would be to use a lifetime on the
next_token
call and a where-clause to ensure ’source lives as long as ’a.pub fn next_token<'a>(&mut self) -> Token<'a> where 'source: 'a { … }
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=876e80d47f1db87430c5b66b9a53b278
Couldn’t
next_token
have just returned the previous and next as a tuple?