09 Rustlings Enums Solution
Rust allows you to define types called "enums" which enumerate possible values. Enums are a feature in many languages, but their capabilities differ in each language. Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. Useful in combination with enums is Rust's "pattern matching" facility, which makes it easy to run different code for different values of an enumeration.
Further information
enums1.rs
// enums1.rs
// No hints this time! ;)
// I AM NOT DONE
#[derive(Debug)]
enum Message {
// TODO: define a few types of messages as used below
}
fn main() {
println!("{:?}", Message::Quit);
println!("{:?}", Message::Echo);
println!("{:?}", Message::Move);
println!("{:?}", Message::ChangeColor);
}
Here we have our code, with not much instruction and no hint this time! But we do have a message in the comments: TODO: define a few types of messages as used below
. Let's take a look at the errors as well.
enums1.rs
errors
⚠️ Compiling of exercises/enums/enums1.rs failed! Please try again. Here is the output:
error[E0599]: no variant or associated item named `Quit` found for enum `Message` in the current scope
--> exercises/enums/enums1.rs:12:31
|
7 | enum Message {
| ------------ variant or associated item `Quit` not found for this enum
...
12 | println!("{:?}", Message::Quit);
| ^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `Echo` found for enum `Message` in the current scope
--> exercises/enums/enums1.rs:13:31
|
7 | enum Message {
| ------------ variant or associated item `Echo` not found for this enum
...
13 | println!("{:?}", Message::Echo);
| ^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `Move` found for enum `Message` in the current scope
--> exercises/enums/enums1.rs:14:31
|
7 | enum Message {
| ------------ variant or associated item `Move` not found for this enum
...
14 | println!("{:?}", Message::Move);
| ^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `ChangeColor` found for enum `Message` in the current scope
--> exercises/enums/enums1.rs:15:31
|
7 | enum Message {
| ------------ variant or associated item `ChangeColor` not found for this enum
...
15 | println!("{:?}", Message::ChangeColor);
| ^^^^^^^^^^^ variant or associated item not found in `Message`
error: aborting due to 4 previous errors
Our errors are pretty straight forward here, we essentially have a repeating messages for each of these errors
no variant or associated item named `Quit` found for enum `Message` in the current scope
--> exercises/enums/enums1.rs:12:31
|
7 | enum Message {
| ------------ variant or associated item `Quit` not found for this enum
Which is telling us that we're missing items in the enum
which of course makes sense since we know that we have to fill out the rest of the enum
. What's nice is that the message is very clear and show's us where our item are missing which is clearly the enum Message
. So let's get to it and fill out that enum
and see what happens.
enums1.rs
solution
#[derive(Debug)]
enum Message {
Quit,
Echo,
Move,
ChangeColor,
}
fn main() {
println!("{:?}", Message::Quit);
println!("{:?}", Message::Echo);
println!("{:?}", Message::Move);
println!("{:?}", Message::ChangeColor);
}
...and this compiles, this is relatively simple as all we had to do is look at the main()
confirmed with the errors
and we've got our complete enum Message
, we didn't need any hints now did we? Here's our output:
Output:
====================
Quit
Echo
Move
ChangeColor
====================
enums2.rs
// enums2.rs
// Execute `rustlings hint enums2` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
#[derive(Debug)]
enum Message {
// TODO: define the different variants used below
}
impl Message {
fn call(&self) {
println!("{:?}", self);
}
}
fn main() {
let messages = [
Message::Move { x: 10, y: 30 },
Message::Echo(String::from("hello world")),
Message::ChangeColor(200, 255, 255),
Message::Quit,
];
for message in &messages {
message.call();
}
}
We have a similar set-up here with what we saw in the previous exercise in which we have to define the different variants that are used in the rest of the code, in this case in our main()
function. Let's take a look at the errors like we always do.
enums2.rs
errors
⚠️ Compiling of exercises/enums/enums2.rs failed! Please try again. Here is the output:
error[E0599]: no variant named `Move` found for enum `Message`
--> exercises/enums/enums2.rs:19:18
|
7 | enum Message {
| ------------ variant `Move` not found here
...
19 | Message::Move { x: 10, y: 30 },
| ^^^^ variant not found in `Message`
error[E0599]: no variant or associated item named `Echo` found for enum `Message` in the current scope
--> exercises/enums/enums2.rs:20:18
|
7 | enum Message {
| ------------ variant or associated item `Echo` not found for this enum
...
20 | Message::Echo(String::from("hello world")),
| ^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `ChangeColor` found for enum `Message` in the current scope
--> exercises/enums/enums2.rs:21:18
|
7 | enum Message {
| ------------ variant or associated item `ChangeColor` not found for this enum
...
21 | Message::ChangeColor(200, 255, 255),
| ^^^^^^^^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `Quit` found for enum `Message` in the current scope
--> exercises/enums/enums2.rs:22:18
|
7 | enum Message {
| ------------ variant or associated item `Quit` not found for this enum
...
22 | Message::Quit,
| ^^^^ variant or associated item not found in `Message`
error: aborting due to 4 previous errors
Errors are similar to last time as well, but we can see that there's something different so let's take a look at how to solve this problem.
enums2.rs
Solution
We can see that in the main()
there isn't simply a name of a enum variant like Move
or Echo
but there's data attached to it. This is because unlike in other languages, enums
in Rust can have data like structs, strings, integers, in fact you can even include another enum
. So in this case we solve this problem by adding the variant with the data that is expected. If we look at Move
we add the { x: i32, y: i32}
struct information. Let's do the same for the rest of the variants.
#[derive(Debug)]
enum Message {
Move { x: i32, y: i32 },
Echo(String),
ChangeColor(i32, i32, i32),
Quit,
}
impl Message {
fn call(&self) {
println!("{:?}", self);
}
}
fn main() {
let messages = [
Message::Move { x: 10, y: 30 },
Message::Echo(String::from("hello world")),
Message::ChangeColor(200, 255, 255),
Message::Quit,
];
for message in &messages {
message.call();
}
}
With that our code compiles, we get our expected output with the different types of data attached. On to our final enum
exercise.
Output:
====================
Move { x: 10, y: 30 }
Echo("hello world")
ChangeColor(200, 255, 255)
Quit
====================
enums3.rs
// enums3.rs
// Address all the TODOs to make the tests pass!
// Execute `rustlings hint enums3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
enum Message {
// TODO: implement the message variant types based on their usage below
}
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) {
// TODO: create a match expression to process the different message variants
// Remember: When passing a tuple as a function argument, you'll need extra parentheses: fn function((t, u, p, l, e))
}
}
#[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);
}
}
In this exercise we see 2 different locations in which we have to complete the code.
- Is the
enum Message
which is completely empty, we've done this a few times so it shouldn't be too difficult.
enum Message {
// TODO: implement the message variant types based on their usage below
}
- The next area is the
process()
function, inside of ourimpl State
in which we get instructions on how to complete by using amatch
expression.
fn process(&mut self, message: Message) {
// TODO: create a match expression to process the different message variants
// Remember: When passing a tuple as a function argument, you'll need extra parentheses: fn function((t, u, p, l, e))
}
}
Now, we take a look at the errors to see if there are any additional hints for completing our task
enums3.rs
errors
⚠️ Compiling of exercises/enums/enums3.rs failed! Please try again. Here is the output:
error[E0599]: no variant or associated item named `ChangeColor` found for enum `Message` in the current scope
--> exercises/enums/enums3.rs:56:32
|
7 | enum Message {
| ------------ variant or associated item `ChangeColor` not found for this enum
...
56 | state.process(Message::ChangeColor(255, 0, 255));
| ^^^^^^^^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `Echo` found for enum `Message` in the current scope
--> exercises/enums/enums3.rs:57:32
|
7 | enum Message {
| ------------ variant or associated item `Echo` not found for this enum
...
57 | state.process(Message::Echo(String::from("hello world")));
| ^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `Move` found for enum `Message` in the current scope
--> exercises/enums/enums3.rs:58:32
|
7 | enum Message {
| ------------ variant or associated item `Move` not found for this enum
...
58 | state.process(Message::Move(Point { x: 10, y: 15 }));
| ^^^^ variant or associated item not found in `Message`
error[E0599]: no variant or associated item named `Quit` found for enum `Message` in the current scope
--> exercises/enums/enums3.rs:59:32
|
7 | enum Message {
| ------------ variant or associated item `Quit` not found for this enum
...
59 | state.process(Message::Quit);
| ^^^^ variant or associated item not found in `Message`
error: aborting due to 4 previous errors
So far, it looks pretty straight forward once again, the compiler is just telling us about the missing variants, so let's start filling out some code.
If we fill out our Message enum
in a similar way we have been in the previous exercises, but instead of passing in the coordinates directly to Move
we pass in Point
because we see that's what we need to match in tests below.
enum Message {
Move (Point),
Echo(String),
ChangeColor((u8, u8, u8)),
Quit,
}
Let's take a look at our next code block that we need to fill in and the instructions. We're told to use a match expression and to remember that when passing a tuple as a function argument we need to add extra parentheses. Alright, noted.
fn process(&mut self, message: Message) {
// TODO: create a match expression to process the different message variants
// Remember: When passing a tuple as a function argument, you'll need extra parentheses: fn function((t, u, p, l, e))
}
}
So here we're going to go through the solution step-by-step since it's something that's a little more than we've had with any of the previous enum
exercises.
process ()
function
fn process(&mut self, message: Message) {
match message {
Message::ChangeColor((r, g, b)) => self.change_color((r, g, b)),
Message::Echo(s) => self.echo(s),
Message::Move(p) => self.move_position(p),
Message::Quit => self.quit(),
}
}
- We see that the
process
function that takes a mutable reference toself
(an instance of theState
struct) and amessage
of typeMessage
. - We use the
match
keyword to pattern match themessage
against different variants of theMessage
enum. - The first match arm
Message::ChangeColor(r, g, b) => self.change_color((r, g, b))
matches theMessage::ChangeColor
variant. It deconstructs the tuple(r, g, b)
by providing individual variablesr
,g
, andb
in the pattern. This allows us to directly access the values ofr
,g
, andb
in theMessage::ChangeColor
variant. Then, it calls thechange_color
method ofself
(an instance ofState
) passing the values(r, g, b)
as arguments. - The second match arm
Message::Echo(s) => self.echo(s)
matches theMessage::Echo
variant. It deconstructs theString
values
from theMessage
enum variant and calls theecho
method ofself
(an instance ofState
) passings
as an argument. - The third match arm
Message::Move(p) => self.move_position(p)
matches theMessage::Move
variant. It deconstructs thePoint
valuep
from theMessage
enum variant and calls themove_position
method ofself
(an instance ofState
) passingp
as an argument. - The fourth match arm
Message::Quit => self.quit()
matches theMessage::Quit
variant. It doesn't need to deconstruct any values, so we simply call thequit
method ofself
(an instance ofState
).
By using the match
expression, we are able to handle different variants of the Message
enum and perform the appropriate actions based on each variant.
enums3.rs
solution
Here's the full block of code for reference
// enums3.rs
// Address all the TODOs to make the tests pass!
// Execute `rustlings hint enums3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
enum Message {
Move(Point),
Echo(String),
ChangeColor((u8, u8, u8)), // remeber the double parentheses
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 {// remeber the double parentheses in ((r, g, b))
Message::ChangeColor((r, g, b)) => self.change_color((r, g, b)),
Message::Echo(s) => self.echo(s),
Message::Move(p) => self.move_position(p),
Message::Quit => self.quit(),
}
}
}
#[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);
}
}
Conclusion
In this post, we gained insights into the usage of enums. We discovered how enums can be used to define different types of messages and handle them effectively. By exploring code examples, we learned how to:
- Define enum variants and handle missing variants to ensure successful compilation.
- Introduce enum variants with associated data for more complex scenarios.
- Extend the concept by implementing methods and matching different message variants.
Overall, we discovered the flexibility and power of enums in Rust, enabling us to handle diverse message types and execute appropriate actions based on the variants.