I'm a Rust newbie, and I started exploring Rust by creating a simple calculator-like program. To parse simple arithmetic I wrote a LR(1) parser that peeks one token ahead. To write my first instinct was to write my own wrapper around Iterator to cache peeked value, fortunately I tried to look into stdlib and found Peekable. It works just fine, except one thing: it's really mouthful to pass it around.
Here's what I'm using now:
fn parse_multiplier<'a, I>(tokens: &mut std::iter::Peekable<I>) -> Tree where
I: std::iter::Iterator<Item = &'a Token> { ...
It does work, but there has to be a simpler way. I expected something like
fn parse_multiplier(tokens: &mut std::iter::Peekable<Item = Token>) -> Tree { ...
to work.
After reading docs, fiddling with it and reading libcore sources I understand why it doesn't work, it just isn't what I expected.
So my questions are:
Am I doing this the right way?
Is there a simpler way for this in Rust currently?
Can there be a simpler way for this in Rust in the future?
Since Peekable provides an extended interface compared to Iterator, there should really be a trait for this.
But there isn't, so you have to do it yourself, like:
Do you really need the parser to be generic over the iterator? You can make it generic, but it does not mean that you have to =) You can define a type alias to abstract over the precise iterator:
type Tokens = Peekable<slice::Iter<Token>>
If you do want to make this generic, you can define your own trait for this:
Ok, this works and looks way nicer. It's like you've hidden all that where clause in the trait and its implementation.
Why isn't this done in libcore though? Is there some reason behind it?
I also don't quite understand: why did compiler force me to add a 'a lifetime specifier in generic arguments? With your code I use PeekableIterator<Item = &Token> and it works just fine w/o explicit lifetime specifier.
Not that I need to but I really want to
I want to be able to make tokenizer lazy in future (make it return Iterator<Token> instead of Vec<Token> as it does now).
@troplin's solution does basically the same as it seems, but it's more abstract, so I like it more
Just a marginally simpler code, because there would be less lifetimes.
but it's more abstract, so I like it more
That's exactly my point: it seems to be easier to start with non generic clone everything code and add generics and zero copy later, when the basic structure is working and you have some tests. I wish I followed this advice myself: I usually start all generic, code myself into a corner with a borrowchecker, flip the table and admit that I don't need a general solution yet
Well, with fn parse_multiplier (tokens: &mut PeekableIterator<Item = &Token>) -> Tree { I don't need any lifetime specifiers (somehow).
BTW, even if I have Copy, I still have to deal with references since Vec<Token> yields &Tokens, not plain Tokens. I wonder if there is an option for that.
Yeah, I'm not it the corner yet, I just have too many details in function definition, more than I like. So generic approach FTW!
Just be aware, that this is not just a shorter form, it's a trait object (i.e. dynamic dispatch).
The template form uses static dispatch, which is usually preferred in Rust.
Ah, that's the catch. Well, while static dispatch is cool, if dynamic dispatch allows for simpler code for a price of some performance, it might fit some niche.