Working with strum Macros in Rust

strum is a crate in Rust which makes working with enums and strings easier. In this blog we can’t cover every single thing that strum can do, but rather just focus on the main features.

Indigo Curnick
October 22, 2024
Articles

Installation is nice and simple, we can just add

strum = { version = "0.26", features = ["derive"] }
strum_macros = "0.26"

To the dependency section of our Cargo.toml.

Let’s start with what I think is the most basic implementation of strum by making an enum an EnumString. You’ll notice throughout this blog that pretty much everything to do with strum uses some kind of macro, which makes it nice and concise.

1use std::str::FromStr;
2
3use strum_macros::EnumString;
4
5fn main() {
6    let colour = Colour::from_str("Red");
7    println!("Colour: {:?}", colour);
8
9    let no_colour = Colour::from_str("No a colour");
10    println!("Not colour: {:?}", no_colour);
11}
12
13#[derive(Debug, PartialEq, EnumString)]
14enum Colour {
15    Red,
16    Green,
17    Blue,
18    Yellow,
19    Black,
20}

Notice how by deriving EnumString on the Colour enum we automatically get the from_str method. from_str returns either the enum variant or an Error - usually something to do with the enum variant not being found. If we run this program we’ll find

Colour: Ok(Red)
Not colour: Err(VariantNotFound)

We can customise some of this behaviour with the following properties

1use std::str::FromStr;
2
3use strum_macros::EnumString;
4
5fn main() {
6    let red = Colour::from_str("r");
7    println!("Red: {:?}", red);
8
9    let not_green = Colour::from_str("Green");
10    println!("Not green: {:?}", not_green);
11
12    let blue = Colour::from_str("bLuE");
13    println!("Blue: {:?}", blue);
14}
15
16#[derive(Debug, PartialEq, EnumString)]
17enum Colour {
18    #[strum(serialize = "red", serialize = "r")]
19    Red,
20
21    #[strum(disabled)]
22    Green,
23
24    #[strum(ascii_case_insensitive)]
25    Blue,
26
27    Yellow,
28
29    Black,
30}

Using #[strum(serialize = "something")]  we can customise the strings that the variant will be deserialised from. Using #[strum(disabled)] we can disable that variant for strum - it won’t deserialize it. We can also use #[strum(ascii_case_insensitive)] to make it so that any variation on upper and lower case spelling is accepted.

The next logical thing to to is to convert the enum into a string. We can use Display for that.

1use strum_macros::Display;
2
3fn main() {
4    let red = Colour::Red;
5
6    println!("Colour: {}", red);
7}
8
9#[derive(Debug, PartialEq, Display)]
10enum Colour {
11    Red,
12    Green,
13    Blue,
14    Yellow,
15    Black,
16}

This program will print

Colour: Red

when run. Obviously this is very easy.

We can customise how this is displayed though.

1use strum_macros::Display;
2
3fn main() {
4    let red = Colour::Red;
5
6    println!("Colour: {}", red);
7}
8
9#[derive(Debug, PartialEq, Display)]
10enum Colour {
11    #[strum(serialize = "red colour")]
12    Red,
13    Green,
14    Blue,
15    Yellow,
16    Black,
17}

This program will now print Colour: red colour when run.

A slightly more efficient program would actually use AsRefStr, which creates a &'a str.

1 use strum_macros::AsRefStr;
2
3fn main() {
4    let red = Colour::Red;
5
6    let word = red.as_ref();
7
8    drop(red);
9
10    println!("{}", word);
11}
12
13#[derive(Debug, PartialEq, AsRefStr)]
14enum Colour {
15    #[strum(serialize = "red colour")]
16    Red,
17    Green,
18    Blue,
19    Yellow,
20    Black,
21}

Now of course the above program won’t compile because the lifetime of word is the same lifetime of the variable red. What else can we do? We can also add a global prefix

1use strum_macros::AsRefStr;
2
3fn main() {
4    let red = Colour::Red;
5
6    println!("Colour: {}", red.as_ref());
7}
8
9#[derive(Debug, PartialEq, AsRefStr)]
10#[strum(prefix = "/")]
11enum Colour {
12    #[strum(serialize = "red colour")]
13    Red,
14    Green,
15    Blue,
16    Yellow,
17    Black,
18}
19

Which would print Colour: /red colour.

The final thing we’ll look at today is adding some custom messages to our enum. We can do this by annotating the enum with EnumMessage from strum_macros, and we also need to import strum::EnumMessage to use them

1use std::str::FromStr;
2use strum::EnumMessage;
3use strum_macros::{Display, EnumMessage, EnumString};
4
5fn main() {
6    let colour = Colour::from_str("Red").unwrap();
7    println!("Colour: {}", colour);
8    println!("Message: {}", colour.get_message().unwrap());
9    println!("Details: {}", colour.get_detailed_message().unwrap());
10}
11
12#[derive(Debug, PartialEq, EnumString, Display, EnumMessage)]
13enum Colour {
14    #[strum(
15        message = "A primary colour",
16        detailed_message = "Red traditionally represents danger"
17    )]
18    Red,
19    Green,
20    Blue,
21    Yellow,
22    Black,
23}
24

This program prints

Colour: Red
Message: A primary colour
Details: Red traditionally represents danger

when run.

That’s all we’ll cover today! I think those are the most useful things that strum has to offer and will cover most use cases. strum makes working with enum in Rust that needs to be a string too significantly easier.

Subscribe To Our Newsletter - Sleek X Webflow Template

Subscribe to our newsletter

Sign up at Naurt for product updates, and stay in the loop!

Thanks for subscribing to our newsletter
Oops! Something went wrong while submitting the form.