1. 11
    1. 6

      I don’t recommend ever putting lifetime annotations on &mut self. In this case it’s sufficient to only name Token’s lifetime:

      impl<'source> Scanner<'source> {
          pub fn next_token(&mut self) -> Token<'source>
      

      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).

      1. 1

        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.)

      2. 1

        …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?

        1. 1
          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
          }
          
    2. 4

      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

      1. 1

        Ooh thanks for this, I’ll look to add it!

    3. 2

      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.

      1. 1

        This is obviously a very subjective take, but I would nonetheless say that:

        1. This specifically has to do with lifetime elision in paths, which are even less explicit because there are no ‘&’
        2. 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)
    4. 2

      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

    5. 1

      Couldn’t next_token have just returned the previous and next as a tuple?