[Avg. reading time: 39 minutes]

Enums

Enumerators

An enum lets you define a type that can be one of a fixed set of variants.

Real-world data comes in messy forms. Example: “Tue”, “Tuesday”, “TUESDAY”, “t”.

If you store that as a String, your code becomes a bug factory: comparisons, validation, reporting, and branching all get fragile.

With an enum, you standardize the values at the type level.

Examples of “finite choices”:

  • Days of the week
  • Months of the year
  • Traffic light colors
#[derive(Debug)]
enum TrafficLight{
    Red,
    Yellow,
    Green
}

fn main(){
    let my_light = TrafficLight::Red;
    println!("{:?}", my_light);
}

Importance of using derive(Debug) Trait.


In addition to simply representing one of several types, we can have additional data based on the value.

// Enum with additional data

#[derive(Debug)]
enum TrafficLight{
    Red (bool),
    Yellow (bool),
    Green (bool)
}

fn main(){
    let my_light = TrafficLight::Red(true);
    println!("{:?}", my_light);
}

Separate the value and enum result


#[derive(Debug)]
enum TrafficLight{
    Red (bool),
    Yellow (bool),
    Green (bool)
}

fn main(){
    let my_light = TrafficLight::Red(false);
    println!("{:?}", my_light);
    
    match my_light {
        TrafficLight::Red(is_active) => println!("Red: {}", is_active),
        TrafficLight::Yellow(is_active) => println!("Yellow: {}", is_active),
        TrafficLight::Green(is_active) => println!("Green: {}", is_active),
    }
}

Enums inside Enums

#[derive(Debug)]
enum TrafficLight {
    Red(bool),
    Yellow(bool),
    Green(bool),
}

#[derive(Debug)]
enum Vehicle {
    Stop,
    Drive(f64),
    CheckLight(TrafficLight),
}

fn main() {
    let my_light = TrafficLight::Red(true);
    let instruction = Vehicle::CheckLight(my_light);

    match instruction {
        Vehicle::Stop => println!("Stop"),
        Vehicle::Drive(speed) => println!("Drive at speed: {}", speed),
        Vehicle::CheckLight(light) => match light {
            TrafficLight::Red(is_active) => println!("Red light: {}", is_active),
            TrafficLight::Yellow(is_active) => println!("Yellow light: {}", is_active),
            TrafficLight::Green(is_active) => println!("Green light: {}", is_active),
        },
    }
}
#[derive(Debug)]
enum TrafficLight {
    Red(bool),
    Yellow(bool),
    Green(bool),
}

#[derive(Debug)]
enum Vehicle {
    Stop,
    Drive(f64),
    CheckLight(TrafficLight),
}

fn main() {
    let instructions = vec![
        Vehicle::Stop,
        Vehicle::CheckLight(TrafficLight::Red(true))
    ];

    for instruction in instructions {
        match instruction {
            Vehicle::Stop => println!("Stop"),
            Vehicle::Drive(speed) => println!("Drive at speed: {speed}"),
            Vehicle::CheckLight(light) => match light {
                TrafficLight::Red(is_active) => println!("Red light: {is_active}"),
                TrafficLight::Yellow(is_active) => println!("Yellow light: {is_active}"),
                TrafficLight::Green(is_active) => println!("Green light: {is_active}"),
            },
        }
    }
}

In Software world, commonly usages are

enum RightClick {
    Copy,
    Paste,
    Save,
    Quit
}

enum FileFormat {
    CSV,
    Parquet,
    Avro,
    JSON,
    XML,
}

enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
    Critical,
}

enum DataTier {
    Hot,
    Warm,
    Cold,
    Archived,
}

Enum Implements

Similar to Struct, you can implement an interface in Enum.

The Implement interface can be helpful when we need to implement some business logic tightly coupled with a discriminatory property of a given enum.


#[derive(Debug)]
enum Shape {
    Circle(f64), // radius
    Rectangle(f64, f64), // width, height
    Triangle(f64, f64, f64) // sides a, b, c
}

impl Shape {
    fn get_perimeter(&self) -> f64 {
        match *self {
            Shape::Circle(r) => r * 2.0 * std::f64::consts::PI,
            Shape::Rectangle(w, h) => (2.0 * w) + (2.0 * h),
            Shape::Triangle(a, b, c) => a + b + c
        }
    }
}

fn main() {
    let my_shape = Shape::Circle(1.2);
    println!("my_shape is {:?}", my_shape);

    let perimeter = my_shape.get_perimeter();
    println!("perimeter is {}", perimeter);
    
    let my_shape1 = Shape::Triangle(3.0,4.0,5.0);
    println!("my_shape is {:?}", my_shape1);

    let perimeter1 = my_shape1.get_perimeter();
    println!("perimeter is {}", perimeter1);
    
    
    let my_shape2 = Shape::Rectangle(4.0,5.0);
    println!("my_shape is {:?}", my_shape2);

    let perimeter2 = my_shape2.get_perimeter();
    println!("perimeter is {}", perimeter2);
    
}

Rust Standard Enums

Option

Many languages use NULL to indicate no value. Errors often occur when using a NULL. Rust does not have a traditional null value.

// Simple Division

fn try_division(dividend: i32, divisor: i32) -> i32 {
    dividend / divisor
}

fn main() {
    println!("{}",try_division(4, 2));
    //println!("{}",try_division(4, 0));
}

Sometimes it’s desirable to catch the failure of some parts of a program instead of calling panic!; this can be accomplished using the Option enum.

// Common Enum

enum Option <T>{
    Some(T),
    None
}

This achieves the same concept as a traditional null value, but implementing it in terms of an enum data type compiler can check to make sure you are handling it correctly.

It’s commonly used and included in the prelude. That means additional use statements are needed.

// Error Handling

// An integer division that doesn't `panic!`
fn try_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // Failure is represented as the `None` variant
        None
    } else {
        // Result is wrapped in a `Some` variant
        Some(dividend / divisor)
    }
}


fn main() {
    let dividend = 4;
    let divisor = 0;
    match try_division(dividend, divisor) {
        None => println!("{} / {} failed!", dividend, divisor),
        Some(quotient) => {
            println!("{} / {} = {}", dividend, divisor, quotient)
        }
    }
}

Result Enum

enum Result<T, E>{
  Ok(T),
  Err(E)
}

Ok(T)

It contains the success value.

Err(E)

Contains the error value

// Simple Simple Interest Function

fn si(p:f32,n:f32,r:f32) -> f32 {
    (p * n * r )/100 as f32
}


fn main(){
    let p:f32 = 10000.00;
    let n:f32 = 3.00;
    let r:f32 = 1.4;

    let si = si(p,n,r);
    println!("Simple Interest = {si}");
}

How to handle if the parameter is Zero?

// SI with conditions

fn si(p:f32,n:f32,r:f32) -> f32 {
    if p <= 0. {
        println!("p cannot be zero");
    }
    
    if n <= 0.{
        println!("n cannot be zero");
    }
    
    if r <= 0. {
        println!("r cannot be zero");
    }
    
    (p * n * r )/100 as f32
}


fn main(){
    let p:f32 = 10000.00;
    let n:f32 = 3.00;
    let r:f32 = 0.0;

    let si = si(p,n,r);
    println!("Simple Interest = {si}");
}

Do you notice any issues with this?

Yes, the message is displayed, but still, the calculation takes place, and the Error message is only for informational purposes.

How does Result enum help in this case?

// using Result

fn si(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn main() {
    let p: f32 = 10000.0;
    let n: f32 = 3.0;
    let r: f32 = 1.4;

    match si(p, n, r) {
        Ok(result) => println!("si = {result}"),
        Err(e) => println!("error occured {:?}", e),
    }
}

Err Demonstration

// using Result

fn si(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn main() {
    let p: f32 = 10000.0;
    let n: f32 = 3.0;
    let r: f32 = 0.0;

    match si(p, n, r) {
        Ok(result) => println!("si = {result}"),
        Err(e) => println!("error occured {:?}", e),
    }
}

Difference when using Result and Option Example

// using Result

fn si_using_result(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn si_using_option(p: f32, n: f32, r: f32) -> Option<f32> {
    if p <= 0. {
        return None;
    }

    if n <= 0. {
        return None;
    }

    if r <= 0. {
        return None;
    }

    Some((p * n * r) / 100.0)
}

fn main() {
    let p: f32 = 10000.0;
    let n: f32 = 0.0;
    let r: f32 = 1.4;

    match si_using_result(p, n, r) {
        Ok(result) => println!("si = {result}"),
        Err(e) => println!("Result ENUM - Error occured {:?}", e),
    }
    
    match si_using_option(p, n, r) {
        Some(result) => println!("si = {result}"),
        None => println!("Option ENUM - Error occurred: one of the inputs is non-positive"),
    }
}

The ? operator

? is used for Error Propogation

There is an even shorter way to deal with Result (and Option), shorter than a match and even shorter than if let. It is called the “question mark operator.”

After a function that returns a result, you can add ?. This will:

If its Ok, return the result If its Err, return the error

Before using ?

fn si_using_result(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn main() {
    let p: f32 = 10000.0;
    let n: f32 = 0.0;
    let r: f32 = 1.4;

    match si_using_result(p, n, r) {
        Ok(result) => println!("si = {result}"),
        Err(e) => println!("Result ENUM - Error occured {:?}", e),
    }

}

Now using ?

Returning Ok()

fn si_using_result(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn main() -> Result<(), String> {
    let p: f32 = 10000.0;
    let n: f32 = 3.0;
    let r: f32 = 1.4;

    let result = si_using_result(p, n, r)?;
    println!("SI calc with {p},{n},{r} = {result}");

    Ok(())
}

Returning Err

fn si_using_result(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn main() -> Result<(), String> {
    let p: f32 = 10000.0;
    let n: f32 = 0.0;
    let r: f32 = 1.4;

    let result = si_using_result(p, n, r)?;
    println!("SI calc with {p},{n},{r} = {result}");

    Ok(())
}

Another way to write using a intermediate function

fn calc_si(p: f32, n: f32, r: f32) -> Result<f32, String> {
    if p <= 0. {
        return Err("Principal cannot be less or equal to zero".to_string());
    }

    if n <= 0. {
        return Err("Number of years cannot be less or equal to zero".to_string());
    }

    if r <= 0. {
        return Err("Rate cannot be less or equal to zero".to_string());
    }

    Ok((p * n * r) / 100.0)
}

fn print_si(p: f32, n: f32, r: f32) -> Result<f32, String>{
    let result_si = calc_si(p,n,r)?;
    Ok(result_si)
}

fn main() {
    let p: f32 = 10000.0;
    let n: f32 = 3.;
    let r: f32 = 1.4;

    println!("{:?}",print_si(p,n,r));

}

Loop through and convert Integers and return Error message for others

// Simple Parse Example

fn parse_str(input: &str) -> Result<i32, std::num::ParseIntError> {
    let parsed_number = input.parse::<i32>()?;
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item);
        println!("{:?}", parsed);
    }
}

unwrap is a method available on Option and Result types that is used to extract the contained value. If the Option is Some or the Result is Ok, unwrap returns the contained value. However, if the Option is None or the Result is Err, unwrap will cause the program to panic and terminate execution.

Use unwrap sparingly and only when you are sure it won’t cause a panic. • Prefer expect with a meaningful message for clarity in cases where you are sure the operation won’t fail.


fn parse_str(input: &str) -> Result<i32, std::num::ParseIntError> {
    let parsed_number = input.parse::<i32>()?;
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["8", "6060"];
    let str_vec1 = str_vec.clone();

    for item in str_vec {
        let parsed = parse_str(item);
        println!("As is: {:?}", parsed);
    }

    for item in str_vec1 {
        let parsed = parse_str(item);
        println!("Unwrapped : {}", parsed.unwrap());
    }
}

unwrap panics


fn parse_str(input: &str) -> Result<i32, std::num::ParseIntError> {
    let parsed_number = input.parse::<i32>()?;
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item).unwrap();
        println!("{:?}", parsed);
    }
}

unwrap_or_else

This can be used in production code.

fn parse_str(input: &str) -> Result<i32, std::num::ParseIntError> {
    let parsed_number = input.parse::<i32>()?;
    Ok(parsed_number)
}

fn main() {
    let str_vec = vec!["Seven", "8", "9.0", "nice", "6060"];
    for item in str_vec {
        let parsed = parse_str(item).unwrap_or_else(|err| {
            println!("Error: failed to parse '{}'. Reason: {}", item, err);
            -1 // Provide a fallback value
        });
        if parsed != -1 {
            println!("{}", parsed);
        }
    }
}

#unrwap #enum #option #resultVer 2.0.12

Last change: 2026-02-18