Comparing enums by variants

Hi everyone,

While building a parser, I encountered a problem I don't know how to solve the idiomatic way. So I have a stream of Token, comming from a JSON file, and I wanted to automate the process of checking if the current token is of a given type, advance the pointer and return true if so, just return false otherwise. Troubles start when I deal with tokens carrying extra values :

#[derive(PartialEq)]
enum Token {
    Add,
    Long(i64)
}

struct Parser {
    input: Vec<Token>,
    pos: usize
}

impl Parser {
    fn matches(&mut self, against: Token) -> bool {
        if std::mem::discriminant(&self.input[self.pos]) == std::mem::discriminant(&against) {
            self.pos += 1;
            return true;
        }
        false
    }
}

fn main() {
    let mut p = Parser { input: vec![Token::Long(5), Token::Add, Token::Long(3)], pos: 0 };
    println!("{}", p.matches(Token::Long(0)));
}

This code works, but I don't really like the idea of constructing some random values to pass to the Parser::matches function. In this particular case, I just want to know if the current token is a Long or not, so I don't care what value it stores, but I can't just pass Token::Long.

I will have other tokens that carry two String for instance, so I'm not really happy with this solution. Maybe I could declare constants for those special cases, but is there another approach I'm not aware of?

Thanks in advance.

1 Like

Something like this?

#![feature(if_while_or_patterns)]

fn matches(&mut self, against: Token) -> bool {
    use Token::*;
    if let (Add, Add) |
           (Long(_), Long(_))
        = (&self.input[self.pos], &against) {
        self.pos += 1;
        return true;
    }
    false
}

(If you aren't on Nightly you can split that or use a match).

In my parser the Token enum has type parameters for the extra data in the tokens, and the syntax depends only on the variant kind. To represent the kind I just plug () to all the type parameters. I did it this way mostly for testing purposes, so I'm not sure it's appropriate in your situation, though.

You can use strum to generate a discriminants enum:

#[macro_use]
extern crate strum_macros;

extern crate strum;

#[derive(Debug, EnumDiscriminants)]
enum State {
    A(u32),
    B(String),
    C
}

fn check(state: &State, expected: StateDiscriminants) -> bool {
    let actual: StateDiscriminants = state.into();
    actual == expected
}

fn main() {
    println!("{}", check(&State::A(42), StateDiscriminants::A));
    println!("{}", check(&State::C, StateDiscriminants::A));
}
1 Like

That's exactly what I was looking for! Thank you so much for pointing out this crate to me.

Kind regards.

1 Like

strum aside, the “vanilla”/canonical way to do this would be adding methods to Token indicating whether a given instance is a certain variant, something like:

enum Token {
    Add,
    Long(i64),
}

impl Token {
    fn is_add(&self) -> bool {
        match self {
            Token::Add => true,
            _ => false,
        }
    }

    fn is_long(&self) -> bool {
        match self {
            Token::Long(..) => true,
            _ => false,
        }
    }
}

struct Parser {
    input: Vec<Token>,
    pos: usize,
}

impl Parser {
    fn matches<F: Fn(&Token) -> bool>(&mut self, f: F) -> bool {
        f(&self.input[self.pos])
    }
}

fn main() {
    let mut p = Parser {
        input: vec![Token::Long(5), Token::Add, Token::Long(3)],
        pos: 0,
    };
    println!("{}", p.matches(Token::is_long));
}
3 Likes

I ran into the same problem when writing a parser and couldn't figure out the 'vanilla' way. Thanks for posting this :pray: