Passing Enum Variant As Argument

Can I use the enum's variant name (without their fields) as a function parameter? I want to iterate through a vector of enum and stop when I found a specific variant. Here is my case:

I have an enum like this:

enum TokenKind {
   Ident(String),
   Let,
   While,
   ...
}

struct Token {
   kind: TokenKind,
   pos: Pos,
}

I want to create a parser that parse a VecDeque<Token> into an AST. In order to do that, I created a helper function like this:

fn skip_until(tokens: &mut VecDeque<Token>, kind: [the TokenKind's variant]) {
   while let Some(tok) = tokens.pop_front() {
      if [tok.kind's variant matches kind] { // this is not possible
         break;
      }
   }
}

I don't think passing an enum's variant (without their field) is possible, but is there something that I miss? I tried to use macro, but there is another case that I can't solve using macro:

fn consume_next(tokens: &mut VecDeque<Token>, kind: [the TokenKind's variant]) -> Option<Token> {
   let Some(tok) = tokens.pop_front() else {
      println!("Expected {}, but found EOF", tok.kind);
   };
   if [tok.kind matches kind] {
      return Some(tok);
   }
   println!("Expected {}, but found {}", kind /* this is not possible, even with declarative macro */, tok.kind);
   None
}
``

If you only need to check which variant it is (ie., equality), then you can use mem::discriminant.

2 Likes

No. An enum's variant is not a type by itself. You need to use the enum as the function parameter.

If you want to compare the kind within Token to the kind parameter received in consume_next, you can also implement the PartialEq trait for the TokenKind enum:

Just be careful when dealing with equality on enums.

As mentioned by @moy2010, enum variants are not types.

You can consider accepting a function as an argument that can do the matching.

use std::collections::VecDeque;

#[derive(Debug)]
pub enum Token {
    A,
    B,
    C,
}


pub fn drain_until(tokens: &mut VecDeque<Token>, predicate: impl Fn(&'_ Token) -> bool) -> Option<Token> {
    while let Some(token) = tokens.pop_front() {
        if predicate(&token) {
            return Some(token)
        }
    }
    None
}

#[test]
fn t() {
    let mut tokens: VecDeque<Token> = [Token::A, Token::B, Token::C, Token::A, Token::B, Token::C].into_iter().collect();
    let token = drain_until(&mut tokens, |token| matches!(token, Token::C));
    assert!(matches!(token, Some(Token::C)));
    assert!(matches!(tokens.make_contiguous(), &mut [Token::A, Token::B, Token::C]));
}

If you want to guarantee a certain token type in the output, you need to create individual types.

use std::collections::VecDeque;

pub struct A(i32);

pub struct B;

pub struct C;

pub enum Token {
    A(i32),
    B,
    C,
}


pub fn drain_until<T>(tokens: &mut VecDeque<Token>, predicate: impl Fn(Token) -> Option<T>) -> Option<T> {
    while let Some(token) = tokens.pop_front() {
        if let Some(token) = predicate(token) {
            return Some(token)
        }
    }
    None
}

#[test]
fn t() {
    let mut tokens: VecDeque<Token> = [Token::A(1), Token::B, Token::C, Token::A(2), Token::B, Token::C].into_iter().collect();
    let token = drain_until(&mut tokens, |token| match token {
        Token::C => Some(C),
        _ => None
    });
    assert!(matches!(token, Some(C)));
    assert!(matches!(tokens.make_contiguous(), &mut [Token::A(2), Token::B, Token::C]));
}

Of course, you could also create another enum which could hold a subset of the original tokens. The structs A, B and C could be any type and hold whatever data is necessary.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.