A `Vec` of `enum`s representing `struct`s--how to add and access the elements?

:wave: I'm learning Rust and implementing a project I know pretty well: parsing text. Given some line of text, I want to identify if it's a Foo or a Bar:

Foooooooooooo
Baaar

(This is not the real project, but it's a close approximation.)

I've got some Rust basics, so I thought I'd like to get a bit fancy. I want to create a Vec of objects that represent each line of data. The closest approximation I got was:

pub struct FooType {
    pub length:i32
}
pub struct BarType {
    pub length:i32
}
pub enum FooBar {
    Foo(FooType),
    Bar(BarType)
}
let mut vec: Vec<FooBar> = Vec::new();
vec.push(FooType { length: 23 });
vec.push(BarType { length: 43 });
let v = vec.pop().unwrap();
assert_eq!(v.length, 23);

(This is roughly real code.)

I thought: ok, I'll have a vector of an enum, and that enum will contain structs, and those structs will have the properties I care about given each line.

However, the code doesn't compile:

Problem One
vec.push(FooType { id: 23 });
^^^^^^^^^^^^^^^^^^ expected enum `tests::it_works::FooBar`, found struct `tests::it_works::FooType`

I called it a vector of FooBar, and I'm trying to pass a FooType. I think I understand the error, but how else should I create a vector so that it can be filled with one of these two types? Switching this to FooBar::FooType yields ambiguous associated type, which I do not understand.

Problem Two
error: no field `length` on type `tests::it_works::FooBar`
  --> src/lib.rs:19:18
   |
19 |     assert_eq!(v.length, 23);
   |  

Ok, so, because it thinks that the type must be that enum FooBar, there's no id field on the enum. How would I fix that? Would that need an impl FooBar that defines an length() method, using match to determine what to perform (call the field length)?

Thanks for any help!

You need to specify the enum variant. Try vec.push(FooBar::Foo(FooType { id: 23 }));

That sounds like a good approach to me!

1 Like

You could also add From implementations, like:

impl From<FooType> for FooBar {
    fn from(v: FooType) -> FooBar {
        FooBar::Foo(v)
    }
}

Then convert the type to the enum a little more implictly:

    vec.push(FooType { length: 23 }.into())

Probably also worth mentioning that your enum variants can be struct-like themselves, like so:

enum FooBar {
    Foo {length: i32}, // or tuple struct form: Foo(i32)
    Bar {length: i32}
}

So unless you intend to use FooType and BarType in other contexts, you don't need them.

1 Like

Great! Thank you! I will have to read up a bit more on this.

Hrm. I'm still misunderstanding something:

impl FooBar {
    pub fn length(self) -> i32 {
        match self {
            FooType => self.length
        }
    }
}

yields:

attempted to take value of method `length` on type `tests::it_works::FooBar`

And even then, I would want something like...

match self {
    _ => self.length
}

Right? Because I can guarantee all the structs have that property.

So unless you intend to use FooType and BarType in other contexts, you don't need them.

Good to know, thank you!

Ah! This blog post was terrific and answered my question.

Hmm, I have one more question on the matter. :sweat_smile:

Suppose I have an impl like this:

impl FooBar {
    pub fn length(self) -> i32 {
        match self {
            FooBar:: Foo(FooType { ref length, .. }) => self.length,
            FooBar:: Bar(BarType { ref length, .. }) => self.length
        }
    }
}

(I plan to make a macro for this, but it's real-ish code.)

Suppose I want to add a new property to FooType, count, but BarType remains with just length:

pub struct FooType {
    length: i32,
    count: i32
}
pub struct BarType {
    length:i32
}

I want to add a new method to the impl to support this iff it is a FooType:

match *self {
    FooBar:: Foo(FooType { ref count, .. }) => { self.count }
}

But of course, the compiler is upset because I didn't match all the possible enum types. What's the Rustful way of resolving this? For now, I've added _ => panic!("That method does not exist for this type."), but that feels really dirty.

Probably change return type to be Option<i32> and return None.

Also, you probably want to take &self in that function, not self, to not consume the value.

1 Like

I think you'd usually write this as:

impl FooBar {
    pub fn length(&self) -> i32 {
        match *self {
            FooBar::Foo(FooType { length, .. }) => length,
            FooBar::Bar(BarType { length, .. }) => length
        }
    }
}
  • Take &self not self, especially if the type is not Copy. This means you have to match on *self, not self.
  • FooBar:: Foo(FooType { length, .. }) creates a new "variable" called length. You can just use it, no need for self.length.
  • ref length is not needed, since length is i32 which is Copy and small.
1 Like