Checking if a Concrete type wrapped in trait is equal to another concrete type

Hi!

I have a trait Statement and a concrete type Let. Let implements Statement.

A function parse_statement returns Result<Box<dyn Statement>, ParseError>.

In tests, I want to compare the results returned by parse_statement with a expected value of type Let I created by calling the constructor.

With some help from folks in rust discord, I have written this code,

       let expected_out = vec![
            Box::new(Let::new(Identifier::new("yr"))),
            Box::new(Let::new(Identifier::new("qq"))),
            Box::new(Let::new(Identifier::new("foobar"))),
        ];
        let lexer = Lexer::new(ip);
        let as_tree = Program::parse(lexer);

        assert_eq!(as_tree.statements.len(), 3);

        for (out, expected_out) in as_tree.statements.into_iter().zip(expected_out.into_iter()) {
            let out: Option<Let> =
                if std::any::TypeId::of::<Let>() == std::any::TypeId::of::<dyn Statement>() {
                    Some(unsafe { std::mem::transmute::<Box<dyn Statement>, Let>(out) })
                } else {
                    None
                };

    if Box::new(out.unwrap()) == expected_out {}

I get the error,

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/parser/program.rs:61:35
   |
61 |                     Some(unsafe { std::mem::transmute::<Box<dyn Statement>, Let>(out) })
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `std::boxed::Box<dyn parser::ast::Statement>` (128 bits)
   = note: target type: `parser::ast::Let` (192 bits)

I kind of understand the error but I am not sure how to do what I am trying to do.

Also, Are there better ways to do this? Hopefully, Without using unsafe?

Hi, if you're ok with limiting Statement to 'static types, you can add a function type_id (for example) to it to get the TypeId. It would be 100% safe and a lot less complicated.

Here is the code in question. https://gitlab.com/ishanjain/monkey-interpreter/blob/master/src/parser/program.rs#L60

Can you please elaborate on what you meant by that? Sorry, I didn't get that.

Something like that.

1 Like

This will help in getting the underlying type but I want to get the data in the concrete type as well. So, I can compare the actual output with the expected out.

Also, Thank you so much for that snippet. :slight_smile:

Isn't this what you want? Or maybe something like this?

1 Like

The Let struct contains an Identifer. I need to make sure the identifier name in the output matches the expected value.

Alright, very non general solution: playground. It will only check the identifier though. Does that seems good?

1 Like

From waht you are describing I guess you try to built an Abstract Syntax Tree. In this case you have usually a limited set of Statements, therefore, I would recommend to build an "enum of new types". Genrally, you build your own structs that implement the Statement trait like the Let you mentioned. Then you create an enum that covers all of your possible Statements, e.g.:

struct Let { id: Identifier }
struct Identifier(String);

imple Statement for Let {}

enum Ast {
    Let(Let),
    // ... other statements
}

impl Ast {
    fn is_let_with_id(&self, id: &str) -> bool {
        if let Let(l) = self {
            &l.id == id
        } else {
            false
        }
    }
}

With this setup you can match against the Ast enum to get the underlying type Let without having to have to work with trait-objects.

2 Likes

Yes, This is what I was doing earlier. https://gitlab.com/ishanjain/monkey-interpreter/blob/98023fe21999b0cf58097aeb39ef61edb7a6da5f/src/parser/ast/mod.rs#L10

I was following along a book and felt like, Since the book is doing this with interfaces, I should try to do the same and then it just got messy.

Thanks for the help, I have reverted it back to using Enums and Thank you @leudz for your help as well. I really appreciate it.