Understanding enums

Hi all -

Very new Rust student here. I'm going through the rustlings exercises (very useful, BTW), and I'm stuck (enums3.rs). The exercise deals with enums, and I'm stuck trying to assign a value from an enum. There are two parts to the code:

    // TODO: implement the message variant types based on their usage below
    ChangeColor((u8, u8, u8)),
    Echo(String),
    Move(Point),
    Quit,
}

I think I have this part correct, though I'm not sure I understand the need for the double parens on ChangeColor (is it to distinguish the tuple from a method?) But this part is giving me an error message:

    fn process(&mut self, message: Message) {
        // TODO: create a match expression to process the different message variants
        match message {
            Message::Quit => self.quit = message.Quit,
            _ => println!("Ain't special"),
        }
    }

The error is:

error[E0609]: no field Quit on type Message

To me, it sure looks like there is indeed a field Quit. What did I do wrong?

Thanks...

The error you are getting is because you are trying to access an enum variant as a struct field:

Message::Quit => self.quit = message.Quit, <-- This cannot be done

You have to use the double colon (::slight_smile: separator to access an enum variant, just as you did in the pattern matching:

Message::Quit => self.quit = Message::Quit
1 Like

@moy2010 You can't use ** for bold in code blocks.

@alice , fixed :sweat_smile:

Thanks for the answer. But...why Message::Quit instead of message::Quit? Don't I want to pick up the argument message that's passed in?

When you write Message::Quit, that creates a new value of type Message with the value Quit. You can also use type message to reuse the one you received as argument, though this may require a clone.

I think I have to use message (not Message) because that's kind of the point of the exercise as I understand it. But when I use "message::Quit" I get this error:

error[E0433]: failed to resolve: use of undeclared crate or module message

Is this related to the need for cloning you mentioned?

Thanks...

I meant just message without the Quit. An enum variant is not a field.

1 Like

I'm sorry, but I'm not sure I understand. If I try:

            Message::Quit => self.quit = message,

I get an error:

expected bool, found enum Message

Which is what I'd expect to see. How do I properly access the contents of message?

The variants of an enum live in their own namespace, so the variants of Message are Message::Quit and so on. The variants act similar to types for things like pattern matching and value construction, but are not distinct types -- a Message::Quit has type Message, as does a Message::Echo(_), etc.

When you have a value of type Message, it's going to be one of the variants. It doesn't have any fields that you can access with .. If it happens to be a data-carrying variant, you have to use something like pattern matching to pull it out, to ensure that you're dealing with the correct variant. So for example:

let message = Message::Echo("foo".to_string());

// other_message has type Message, and after assignment
// is the variant Message::Echo
let other_message = message;

// If you wanted to get the data out
let mut inside_data = String::new();
if let Message::Echo(data) = other_message {
    inside_data = data;
}

And for non-data carrying variants, it works similarly...

let message = Message::Quit;

// other_message has type Message, and after assignment
// is the variant Message::Quit
let other_message = message;

But there isn't any data to get out. But also since they have no data, you can always just create a new Message::Quit on the fly.


enums don't have fields and make you match on variants because they are tagged unions, and safety demands that you always dynamically check what variant you have. If you have a Message::Echo, you definitely can't also have a Message::Quit, so they act less like fields. This is in contrast with untagged unions, where the compiler doesn't know what variant you have, and you treat them like fields. (Rust has unions too, for FFI reasons mainly, but they are unsafe to use.)

(There's also some desire to turn enum variants into they're own types, but this would be a large change and isn't likely any time soon.)

You can import from an enum namespace too, for example:

fn foo(message: Message) {
    use Message::*;
    match message {
        // No need for Message::Name anymore
        ChangeColor(tuple) => {},
        Quit => {},
        _ => { /* etc */ },
    }
}

Which you're probably already using all the time with Results (Ok(_), Err(_)) and Options (Some(_), None).


If they had declared it

    // ...
    ChangeColor(u8, u8, u8),
    // ...

You wouldn't need a tuple. But they defined it as a single value that is a tuple. Perhaps it's common for the code in question to be passing around such tuples.


I haven't looked at the problem they gave, but if self.quit is a bool, you probably want something like

    Message::Quit => self.quit = true,

There is no "content" to a Message::Quit beyond the fact that it's the Quit variant of a Message. You could assign it to a variable of type Message (or just as easily assign Message::Quit to a variable of type Message). But it carries no extra data, so if you need to infer some sort of data like a bool, you have to supply that data explicitly -- there's nothing there to be pulled out of the variable itself.

5 Likes

Thank you for the very detailed response. After re-reading the exercise (several times), I believe you are correct about my wanting to use:

Message::Quit => self.quit = true,

Now I'm trying to create the arm for Echo. I'm trying this:

Message::Echo(String) => self.echo(message),

which gives me the following error:

expected struct String, found enum `Message

(referring to message). Why isn't it interpreting message as the variant?

Thanks...

You can use a pattern like Message::Echo(x), which will create a variable x to hold the string:

Message::Echo(x) => self.echo(x),

(You can use any variable name in place of x.)

Both the left part of a match arm:

    Message::Echo(x) => self.echo(x),
//  ^^^^^^^^^^^^^^^^

And the part between let and = (or :):

    if let Message::Echo(data) = other_message {
//         ^^^^^^^^^^^^^^^^^^^

Is a pattern that you can use to bind new variables (x, data) to existing variables... or internals or other parts of existing variables (also known as destructuring). In this case, it's how to move the String out of the Message::Echo(_).

The chapter I linked to in the book is sort of a long chapter; there are a lot examples in section 18.3. In particular, the part about destructuring enums uses an enum very similar to the one we've been working with in this thread.

So...even though the match arm:

Message::Echo(x) 

makes no mention of the variable message, the fact that message is the basis of the "match" statement is how it knows to use message as the source for x? (I realize this isn't well-worded; sorry.)

Right, at the start of the match when you do

    match message {

this means each of your match arm patterns are applied against message, so when you get to

        Message::Echo(x) => { /* ... */ }

if the pattern matches, a new variable x is bound to the contents inside of message. (Usually this means moving/copying the contents out of message and into x.) Then inside the { /* ... */ }, you can use the new variable x.

The same thing happens if you do this:

if let Message::Echo(x) = message { /* ... */ }

It's just arranged in a different way, let's you try multiple variants, and in fact throws an error if you don't handle every possible variant. Which is a good thing.


With Echo(x), because you're moving the String out of message and into x, you can no longer use message after this. But moving the String out is the point in this case, so that's fine. There are ways to take references instead of moving/copying, but I suggest getting used to patterns generally for now, and looking at more complicated cases later or when they come up in the practices you're working through.

3 Likes

Thanks again.

Are we supposed to mark threads as solved here?

It’s not required, but appreciated. :slight_smile:

OK, will do...is it just a matter of checking the solution box, or is there more to it?

Yes, just checking the solution box is it.

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.