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


#1

: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!


#2

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

That sounds like a good approach to me!


#3

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())

#4

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.


#5

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!


#6

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


#7

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.


#8

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.


#9

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.