🎓 A1Learner
🔍
0 of 142 lessons completed

Rust Enum Variants

Enum variants are the named kinds of an enum, and each variant can carry its own data.

Three shapes of variant

A variant takes one of three shapes: no data, like Quit; a tuple of values, like ChangeColor(u8, u8, u8); or named fields, like Move { x: i32, y: i32 }. One enum can therefore hold what would otherwise be several separate structs:

#[derive(Debug)]
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8) }
let m = Message::Move { x: 3, y: 7 };
println!("{:?}", m);

One function accepts any variant

All the variants are still one type, so one function can accept any of them:

fn report(m: &Message) {
    match m { Message::Quit => println!("quit"), _ => println!("some message") }
}
report(&Message::Write(String::from("hi")));
report(&Message::Quit);

Enums can have methods, too

Use impl on an enum, just like on a struct, to add methods that work on self. Inside an enum method, match on self to do a different job for each variant:

enum Shape { Circle(f64), Square(f64) }
impl Shape {
    fn area(self) -> f64 { match self { Self::Circle(r) => 3.14159 * r * r, Self::Square(s) => s * s } }
}
println!("area = {}", Shape::Square(3.0).area());

Drop the prefix with use

One use line brings every variant into scope, so Advanced works on its own — in match arms too:

#[derive(Debug)]
enum Stage { Beginner, Intermediate, Advanced }
use Stage::*;
let now = Advanced;
println!("now = {:?}", now);

Or import only some variants

Import just some variants when you prefer — any other variant still needs its full Stage:: name:

use Stage::{Beginner, Advanced};
let a = Beginner;
let b = Stage::Intermediate;
println!("{:?} {:?}", a, b);

Example

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}
impl Message {
    fn describe(&self) -> String {
        match self {
            Message::Quit => String::from("quit"),
            Message::Move { x, y } => format!("move to ({}, {})", x, y),
            Message::Write(text) => format!("write: {}", text),
            Message::ChangeColor(r, g, b) => format!("colour ({}, {}, {})", r, g, b),
        }
    }
}
fn main() {
    let a = Message::Write(String::from("hello"));
    let b = Message::Move { x: 3, y: 7 };
    let c = Message::ChangeColor(255, 0, 0);
    println!("{}", a.describe());
    println!("{}", b.describe());
    println!("{}", c.describe());
    println!("{:?}", Message::Quit);
}

Key points

  • #[non_exhaustive] on a pub enum promises 'more variants may come'.
  • Code in other crates must add a _ arm to its match.
  • A library can grow the enum later without breaking its users.
  • Self is a short name for the enum itself, so Self::Square means the same as the full enum name plus ::Square.

In this example, Message has four variants in all three shapes. Quit carries nothing. Move carries named fields x and y. Write carries one String. ChangeColor carries three numbers. The describe method matches on self. It builds a line of text for each variant. It pulls the data out as it goes. main builds three messages and prints each description. The last line prints Message::Quit with {:?}, which shows the bare variant name: Quit.

Note

Many languages need a whole family of classes for this job.

In Rust, one enum plus one match does the job, and the compiler checks that every variant is handled.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

In Move { x: i32, y: i32 }, what shape is the variant?