Hello all, I'm just starting to learn rust and I'm using it on a project to build a parser for a super basic assembly language. I stated following along with this tutorial Writing a Simple Parser in Rust but I think its a bit out of date. In that tutorial the author uses iterators for the lexer but not for the parser. In my code I've added iterators for parsing stage as well.
As part of introducing iterators, the rust compiler informed me that I needed to introduce a lifetime to iterator type. Now I don't know anything about lifetimes and I blindly followed the compiler, but I want to understand if what I'm doing is right, what are the implications of introducing a lifetime, is there a way to write this without introducing and explicit lifetime?
I've linked the relevant section of my code below from github.
Named lifetimes or not, when you have an iterator over borrowed data, the implications are that you can only hold on to the iterator items so long as the lifetime is valid. Within a function body with a lifetime parameter (like parse_reg), it's a given that the lifetime is valid for at least as long as just after the function call. Since you're not trying to send the items elsewhere, and not returning anything that stores any iterator items, there's not much to worry about in this code.
If you start returning borrowed values instead of owned things like u8s, you might have to start paying attention to how the borrowing flows.
Thanks for the response that makes sense! I guess the cases where I would have to be more careful is if I the lifetime extended beyond the scope of the function, correct? Like if I was storing the data in some structure or returning it from the function?
In my case right now the lifetime only exists as long as the function exists so I don't really have anything to worry about?
Once you start doing things like that, you're more likely to run into borrow check errors because something remains borrowed while you try to use it in an incompatible way, yes.
As a function caller, if you're only calling functions that take anonymous or otherwise unconstrained lifetimes such as the ones here, and they don't return them, it's usually not a problem. Maybe if you try to pass multiple references to the same thing and one or more of them is &mut. From that perspective the lifetimes generally only exist as long as the function call.
Within the function, the perspective is that the caller chooses the lifetime so there's no upper cap, and the lower cap is just longer than the function body. If you're just using the things and discarding them, it's usually not a problem. Perhaps the most common problems would be trying to use them too long (like trying to send them to a different thread which requires them to be 'static) or mixing them up with local borrows that are too short (because you can't borrow a local for longer than the function body).
But in general don't worry about these anonymous/unconstrained lifetimes in your function signatures, especially if you're not returning them too. You've already been using them every time you take a reference (fn foo(s: &str, we: &Whatever)), even though you haven't had to give them names. And you're using even more complicated ones that do return borrows, like whatever created the iterator you're passing into this function, and it apparently hasn't been a problem yet.
So I'd say continue on as you have been. Learning more can wait until you reach some point where you're getting borrow check errors.