DEV Community

loading...

Getting started with Rust: Enum on steroids!

mnivoliez
Developer, studying to work in game engines. Rust, JS, Bash
・4 min read

Originally posted on my blog

Hello everyone! Today subject was hard to decide on. But as the previous one was pretty tedious, I decided to go a subject more easy to speak of. So, today we are going to talk about enum in Rust!

"Yeah,.... but what is an enum anyway?"

Let me explain this first. An enum (or enumeration) is a type where all possible value are listed.
To give an example, let say that I can take care of different dogs: doggo, doge and pupper.

Then we can define a enum to represent that:

enum DogKind {
  Doggo,
  Doge,
  Pupper
}
Enter fullscreen mode Exit fullscreen mode

Let's decompose that. First we use the keyword enum to express that we are going to define an enum. Then there is the name of the enum, in our case it is DogKind. Then, between { ... } we define all possible value of DogKind separate with comas.

We are now able to use it as this:

fn take_care_of_dog(kind: DogKind) {
  match kind {
    DogKind::Doggo => take_care_of_doggo(),
    DogKind::Doge => take_care_of_doge(),
    DogKind::Pupper => take_care_of_pupper(),
  }
}
Enter fullscreen mode Exit fullscreen mode

Do you remember in the previous posts when I said that it is a good practice to read out loud the code? Enum in Rust are a good practice for that. Let do it for training.
We declare a function fn called take_care_of_dog which take a parameter kind of type DogKind. We know that DogKind is an enum define before. Then we check if which value of DogKind the parameter kind matches. And for every possible match, we define an action to follow. Here the arrow can be read as then, do that, so the match statement can be read as if it match Doggo, then do that.

"Ok, I have use other language before, this is just a switch case, no?"

Fair enough. But it is only the begining. Let me show you something really fun.

Let take again our example from before, upgraded a bit:

enum DogKind {
  Doggo(String),
  Doge(String, String, String),
  Pupper { age: f32 }
}
Enter fullscreen mode Exit fullscreen mode

"What the f... ... Freacking duck?"

That usually the response if you have never used language with tagged union.
Let me explain, for each value of DogKind, we want to have specific values, specific properties.
You can use this like that:

fn say_something_about_dog(kind: DogKind) {
  match kind {
    DogKind::Doggo(name) => println!("Good boy, {}", name),
    DogKind::Doge(such, wow, much) => println!("Such {}, wow {}, much {}", such, wow, much),
    DogKind::Pupper { age } => println!("Oh this pupper is {} years old", age)
  }
}
Enter fullscreen mode Exit fullscreen mode

"Wow, such variant... But are we oblidge to express all possible case into the match statement?"

The match is exhaustive, meaning that you have to describe ALL cases.
Well, there is a syntaxic element that allow you describe the "default case": the _ placeholder.

fn say_something_about_dog(kind: DogKind) {
  match kind {
    DogKind::Doggo(name) => println!("Good boy, {}", name),
    DogKind::Doge(such, wow, much) => println!("Such {}, wow {}, much {}", such, wow, much),
    DogKind::Pupper { age } => println!("Oh this pupper is {} years old", age),
    _ => println!("What is that? it's not a dog... Is it alive?"),
  }
}
Enter fullscreen mode Exit fullscreen mode

But, I cannot stress you enough that the default case is not robust. For example, let say that you add a kind of dogs: the Doggy. Then no match statement with the _ will fail to compile because this new kind will fall into the default case.
Again, the _ is not evil, it is a powerful too that must be use carefully.

"What about doing the same thing for different value?"

We can do that easily with the |:

enum DogKind {
  Doggo,
  Doggy,
  Doge,
  Pupper
}

fn great_dog(kind: DogKind) {
  match kind {
    Doggo | Doggy => println!("Who is a good boy? It's you!"),
    Doge => println!("Wow, such elagance, much raffinment"),
    Pupper => println!("How adorable!"),
  }
}
Enter fullscreen mode Exit fullscreen mode

"Ok, are they other things we have to know about enum and match expression?"

Yeah. First you can use brackets inside match expression:

enum DogKind {
  Doggo,
  Doggy,
  Doge,
  Pupper
}

fn great_dog(kind: DogKind) {
  match kind {
    Doggo | Doggy => println!("Who is a good boy? It's you!"),
    Doge => println!("Wow, such elagance, much raffinment"),
    Pupper => {
      println!("How adorable!");
      println!("I pet the pupper");
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

And you can use match expression as assignment.

"Wait... what?"

Yep! Let say that you got a value that depends of the type of dog, you could write something like:

enum DogKind {
  Doggo,
  Doggy,
  Doge,
  Pupper
}

fn great_dog(kind: DogKind) {
  let action = match kind {
    Doggo | Doggy | Pupper => "Pet the dog",
    Doge => "Pay your respect to the venerable doge"
  }; // note the ";" at the end of the assignment expression.
  println!("{}", action); 
}
Enter fullscreen mode Exit fullscreen mode

"Ok, seems useful..."

Yeah it is, and you want to know something? You probably already use it.
The Result<T,E> and Option<T> are enum themself.
And they are a good example of enum with traits...

But we will see traits the next time :p

So, see you next time for more, do not hesitate to comment any error, suggestion or anything about this post.

-- Mathieu

Discussion (5)

Collapse
evancarroll profile image
Evan Carroll • Edited
fn take_care_of_dog(kind: DogKind) {
  match kind {
    DogKind::Doggo => take_care_of_doggo(),
    DogKind::Doge => take_care_of_doge(),
    DogKind::Pupper => take_care_of_pupper(),
  }
}

Can as of new rust be written like this,

impl DogKind {
  fn take_care(&self) -> u8 {
    match self {
      Self::Doggo => take_care_of_doggo(),
      Self::Doge => take_care_of_doge(),
      Self::Pupper => take_care_of_pupper(),
    }
  }
}
Collapse
mnivoliez profile image
mnivoliez Author

True. The question is now is there any difference between the two code after compile. I'll check that layer :)

Collapse
pointyfluff profile image
PointyFluff

zero cost abstractions?

Collapse
hgisinger profile image
Hernando Gisinger

Great post

Same function in all cases?

DogKind::Doggo => take_care_of_doggo(),
DogKind::Doge => take_care_of_doggo(),
DogKind::Pupper => take_care_of_doggo(),

Collapse
mnivoliez profile image
mnivoliez Author

Seems like I made a little mistake. Thanks for the hint. Let me correct that!