Typecasting a enum type for extracting values

I am working on building a programming language. And I have this type of AST enum of expressions

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    IntExpr {
        token: Token,
        value: i64,
    },
    BoolExpr {
        token: Token,
        value: bool,
    },
    //[...]

For extracting the underlying value from the enum variant I have two options either use match or an if let. For some specific cases, I am sure what the enum variant is going to be, and I am doing something like this to extract value

let left: Object; // <-- got from function return

// For IntExpr
let val: Option<i64> = if let Object::Number { token: _, value } = left {
    Some(value)
} else {
    None
};

Then I use values with .unwrap() as it is always going to ok with no chance of panics.

In Golang, I can do something like this (with interfaces)

val := left.(*object.Number).Value

Is there any optimal or correct way to do what I am doing to extract values of enums?

Why do you think that the current approach is incorrect or suboptimal? Have you found a case where it works erroneously? Have you benchmarked and found that it is too slow?

You might want to consider

pub enum Expr {
    IntExpr(IntExpr),
    // ...
}

pub struct IntExpr {
    token: Token,
    value: i64,
}

This way, you should be able to refactor "Expr that I know is an Expr::IntExpr" into just IntExpr in most cases.

Another alternative would be let else:

let Expr::Number { value, .. } = left else {
    panic!()
};

This would give you the value without the wrapping Option and immediately panic if left isn't an Expr::Number.

2 Likes

That is a very beautiful solution.

I also like to just implement methods on Expr like as_i64, as_bool, etc., like serde_json does:

#[derive(Debug, Clone, PartialEq)]
pub struct Token;

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    IntExpr {
        token: Token,
        value: i64,
    },
    BoolExpr {
        token: Token,
        value: bool,
    },
    //[...]
}

impl Expr {
    fn as_i64(&self) -> Option<i64> {
        match self {
            Self::IntExpr { value, .. } => Some(*value),
            _ => None,
        }
    }
}

fn main() {
    let left = Expr::IntExpr { token: Token, value: 0 };
    
    let val = left.as_i64().unwrap();
    
    assert_eq!(val, 0);
}

Playground.

I am gonna use the second one. Thanks

This is useful, I am gonna use that. Thanks

If you write your type as so:

pub struct IntExpr {
  token: Token,
  value: i64,
}

pub enum Expr {
  IntExpr(IntExpr),
  BoolExpr(BoolExpr),
}

then you can use derive_more to generate the injections you want, e.g.


#[derive(TryInto)]
#[try_into(owned, ref, ref_mut)]
pub enum Expr {
  IntExpr(IntExpr),
  BoolExpr(BoolExpr),
}

fn test(left : Expr) -> Option<i64>
{
  left.try_into().map(|i : IntExpr| i.value).ok()
}

and can write borrowing versions easily

fn test2(left : &Expr) -> Option<&i64>
{
  left.try_into().map(|i : &IntExpr| &i.value).ok()
}

... but prefer pattern matching where appropriate.

There's also enum_as_inner - Rust

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.