Super basic question about enums and structs

Hello everyone!

I am doing the rustlings book and came across this struct/enum exercise (it's enums3)
I promise I read the book parts 6.1 and 6.2, and this thread.

  1. But yet: I've had the hardest time to get why does the Message::ChangeColor variant does not take a State.color as type?
    Move takes a Point, so why would ChangeColor, who implements a color tuple, not take a color as argument? I guess my problem is that I am under the impression that those enums need to be very hierarchical and that the enum Message should not be able to access an inner field of another struct that is not even public.

  2. Besides the compiler that if I was going that way ( ChangeColor(State::color)), I should instead implement State as trait:
    ^^^^^^^^^^^ help: use fully-qualified syntax: ::color`
    So it got me even more confused :smile:. What does this error message even mean?

  3. (last minute edit): why doesn't the compiler complain about the missing placeholder _ for every other processing case :face_with_monocle: ?



enum Message {
    ChangeColor(u8,u8,u8),
    Echo(String),
    Move(Point),
    Quit,
}


struct Point {
    x: u8,
    y: u8
}

struct State {
    color: (u8, u8, u8),
    position: Point,
    quit: bool
}

impl State {
    fn change_color(&mut self, color: (u8, u8, u8)) {
        self.color = color;
    }

    fn quit(&mut self) {
        self.quit = true;
    }

    fn echo(&self, s: String) {
        println!("{}", s);
    }

    fn move_position(&mut self, p: Point) {
        self.position = p;
    }

    fn process(&mut self, message: Message) {
        match message {
            Message::ChangeColor(a,b,c) => self.change_color((a,b,c)),
            Message::Quit => self.quit(),
            Message::Echo(s) => self.echo(s),
            Message::Move(p) => self.move_position(p),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_match_message_call() {
        let mut state = State{
            quit: false,
            position: Point{ x: 0, y: 0 },
            color: (0, 0, 0)
        };
        state.process(Message::ChangeColor(255, 0, 255));
        state.process(Message::Echo(String::from("hello world")));
        state.process(Message::Move(Point{ x: 10, y: 15 }));
        state.process(Message::Quit);

        assert_eq!(state.color, (255, 0, 255));
        assert_eq!(state.position.x, 10);
        assert_eq!(state.position.y, 15);
        assert_eq!(state.quit, true);
    }

}

Because Point is a type, color is a variable. You could either create a newtype for the color, which contains three u8, or define a type alias, like type Color = (u8, u8, u8) and replace every instance of (u8, u8, u8) by Color.
You can't accept an enum variant as parameter as well. Only the "whole" enum.


This is related to trait types. You will encounter them later :wink: don't get too confused by now.


I'm not entierly sure what you mean with your third question. I try to guess:

If you prepend a variable by an underscore it will be marked as unused. For some more questions and answers see this SO post.

I think @2e71828 is spot on for your third question :wink:

Hope that helps, if not, ask again and more :wink:

1 Like

I’m assuming that you’re confused by the lack of an _ => ... arm in this match statement:

Match requires that every possible value gets selected by one of the patterns. In this case, the compiler can see that you’ve already matched every possible variant of message and thus don’t need a wildcard pattern.

1 Like

Thank you both!

I tried that, and it compiles, you're right (of c :face_with_hand_over_mouth:). Right, let's forget about trait types for now...

type Color = (u8, u8, u8);

struct State {
    color: Color,
    position: Point,
    quit: bool
}

Why is point a type? I thought it was a struct?

Yes. So since the compiler could see that a function was implemented for all the Message types, it did not complain. Had I had another function, it would have refused?

A type is a superset of every possible "thing" a variable can be. From booleans over floating and integer types, as well as structs, enums and unions. For a more complete list see Types - The Rust Reference


It's not about the function, but the match statement. A match statement must always be exhaustiv, which means it must cover all possible variants. If you for example match an integer

fn foo(a: u32) -> &str {
    match a {
        0 => "Zero",
        1 => "One",
    }
}

The compiler would complain: pattern `2u32..=std::u32::MAX` not covered. But if you are covering all possible values (4 in the example of your enum), the compiler sees that you have covered every possible value and is fine with it :slight_smile: If you want to cover the other solutions in my example, you could simply add _ => todo!("I'm too lazy for this!") as an match arm and the compiler will accept it.

1 Like

Oh thanks. I thought it was the other way around, that type was at the bottom of the sandwich!

Maybe it's confusing because there are Types and there is the type keyword.

1 Like

haha... oh gosh.

@hellow thanks, this was exactly why actually....

1 Like

Last one if it's ok.
Why is Quit a unit struct, yet when implemented by State it takes a boolean value?

Quit is an enum variant, you're right, but quit is a variable of type bool.

Just because two items share a similar (or same) name, doesn't necessarily mean they are connected. In Rust you declare the variable name first, then a color : and afterwards the type of the variable.

1 Like

To add to what @hellow said, even if the two things have the same name, they wouldn't clash, because they reside in different scopes — one is Message::Quit, and the other is a field of State.

This saves us from having to always come up with a globally unique name — there aren't many good names for us to choose from!

1 Like

Oki. So message has a type Quit, that works on a State. So implementing the Message::Quit on state in turns calls the method quit (that has nothing to do with the message).

I tried to expand the example to see how stuff clash or does not, so I made a new struct black that tries to access Message. Please :bear: with me.

How can I bring into scope values that have been declared in structs, to send them into implementations?

Is there a way to replace this line

    black.process(Message::ChangeColor(12,12,12));

with something like

black.process(Message::ChangeColor(black.color)));
// enums3.rs
// Address all the TODOs to make the tests pass!


enum Message {
    ChangeColor(u8,u8,u8),
    Echo(String),
    Move(Point),
    Quit,
}


struct Point {
    x: u8,
    y: u8
}

type Color =  (u8, u8, u8);
struct State {
    color: Color,
    position: Point,
    quit: bool
}


impl State {
    fn change_color(&mut self, color: (u8, u8, u8)) {
        self.color = color;
    }

    fn quit(&mut self) {
        self.quit = true;
    }

    fn echo(&self, s: String) {
        println!("{}", s);
    }

    fn move_position(&mut self, p: Point) {
        self.position = p;
    }

    fn process(&mut self, message: Message) {
        match message {
            Message::ChangeColor(a,b,c) => self.change_color((a,b,c)),
            Message::Quit => self.quit(),
            Message::Echo(s) => self.echo(s),
            Message::Move(p) => self.move_position(p),
        }
    }
}
struct Black{
    color: (u8,u8,u8),
    quit: String,
}

impl Black{
    fn assign_color(&mut self){
        self.color = (0,0,0);
    }

    fn echo(&mut self, s: String){
        self.quit = s;
    }

    fn process(&mut self, message: Message){
        match message{
            Message::ChangeColor(a,b,c) => self.assign_color(),
            Message::Echo(s)=> self.echo(s),
            _ => println!("TODOs"),
        }
    }

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_match_message_call() {
        let mut state = State{
            quit: false,
            position: Point{ x: 0, y: 0 },
            color: (0, 0, 0)
        };
        state.process(Message::ChangeColor(255, 0, 255));
        state.process(Message::Echo(String::from("hello world")));
        state.process(Message::Move(Point{ x: 10, y: 15 }));
        state.process(Message::Quit);

        assert_eq!(state.color, (255, 0, 255));
        assert_eq!(state.position.x, 10);
        assert_eq!(state.position.y, 15);
        assert_eq!(state.quit, true);
    }

    #[test]
    fn test_another_struct(){
        let mut black = Black{
            color:(12,12,12),
            quit: "Rum".to_string(),
        };

    black.process(Message::Echo("Black is the new Black".to_string()));
    black.process(Message::ChangeColor(12,12,12));

    assert_eq!(black.color, (0,0,0));
    assert_eq!(black.quit, "Black is the new Black".to_string());
    }

}

No, Quit is not a type, it's an enum variant. The type is Message. This is an important distinction, similar to how 2 or "Hello World!" are not types, but values.

The problem is that black.color is a single value of type (u8,u8,u8), but the ChangeColor variant takes three fields, each of type u8. There's a difference between multiple fields and a single field of tuple-type. You can either modify the enum to have a single field of type (u8,u8,u8) like this:

enum Message {
    ChangeColor((u8,u8,u8)),
    Echo(String),
    Move(Point),
    Quit,
}

Alternatively, you could define a function for doing the conversion:

enum Message {
    ChangeColor(u8,u8,u8),
    Echo(String),
    Move(Point),
    Quit,
}

impl Message {
    fn color_from_tuple(tuple: (u8,u8,u8)) -> Message {
        Message::ChangeColor(tuple.0, tuple.1, tuple.2)
    }
}

black.process(Message::color_from_tuple(black.color)));
2 Likes

Thanks @alice, super clear as always. So there is no solution to extract the tuple from the depth of the struct variant (??) black.

I really like your function-conversion.

No, there is no automatic way to turn a tuple into multiple arguments.

You use the word "variant" only for enums, and the stuff in a struct are called fields. Additionally, the variants in an enum can have fields. For example, the ChangeColor variant of the Message enum has three fields, each of type u8.

struct MyStruct {
    a_field: SomeType,
    another_field: SomeOtherType,
}
enum MyEnum {
    AVariant(TypeOfAField, TypeOfAnotherField),
    AnotherVariant {
        a_field: SomeType,
        another_field: SomeOtherType,
    }
}

The Quit variant of Message doesn't have any fields.

1 Like

Re

If I embroider on your example (before I stop the neverending thread :hugs:), would

enum MyEnum {
    AVariant(TypeOfAField, TypeOfAnotherField),
    AnotherVariant(MyStruct)
    }

compile, since it's the same content?

Yes, that would also compile, but it is not the same thing. In your example, the AnotherVariant variant of MyEnum has a single field of type MyStruct, whereas in my example the AnotherVariant variant has two fields: a_field and another_field.

It's the same situation as the three-element tuple.

1 Like

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.