Rise In Logo





Rust Programming for Smart Contract Development

Simple introduction to Option and Result

In this section, we will learn the fundamental concepts of Option and Result enums. These two types are crucial in Rust, as they help handle the absence of values and manage error handling in a concise and expressive manner. 

Introduction to Option

In Rust, Option is an enum that represents the possibility of a value being present or absent. It is a generic enum that can hold any type T. It has two variants:

  • Some(T): Represents a value of type T
  • None: Represents the absence of a value

The Option enum is defined in Rust's standard library as follows:

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


As you can see, it is a generic enum, which means you can use it with any data type by specifying the type T. The Some variant wraps a value of type T, while the None variant represents the absence of a value.

Don’t worry if you are confused with the generic concept. We will learn more about generic in the following chapters. What you need to know is the <T> is basically a placeholder for data type. For an example, if we provide String for <T> then we would have Some(String) where String = <T>.

Example of using Option

Consider a scenario where you need to find the square root of a number. The square root of a negative number is not a real number. To handle this, you can use the Option enum:

fn find_square_root(number: f64) -> Option<f64> {
    if number >= 0.0 {
        Some(number.sqrt())
    } else {
        None
    }
}

fn main() {
    let number = -4.0;
    let square_root = find_square_root(number);

    match square_root {
        Some(value) => println!("The square root of {} is: {}", number, value),
        None => println!("The square root of {} is not a real number.", number),
    }
}


In this example, find_square_root calculates the square root of a number if it's non-negative, returning Some(square_root). Otherwise, it returns None. The match statement in the main function then handles the two possible cases, printing the result accordingly.

Introduction to Result

Result is another essential enum in Rust, used for handling errors and returning meaningful information about the cause of the error. It is a generic enum that can hold two types T and E. Result has two variants:

  • Ok(T): Represents a successful operation with a value of type T
  • Err(E): Represents an error with a value of type E

The Result enum is defined in Rust's standard library as follows:

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


It is a generic enum with two type parameters, T for the success case and E for the error case. The Ok variant wraps a value of type T, while the Err variant wraps a value of type E.

Example of using Result

Let's consider the previous division example. We can use the Result enum to handle the possibility of division by zero:

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero is not allowed.".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    let a = 10.0;
    let b = 0.0;
    let division_result = divide(a, b);

    match division_result {
        Ok(value) => println!("{} divided by {} is: {}", a, b, value),
        Err(error_message) => println!("Error: {}", error_message),
    }
}


In this example, the `divide` function returns a `Result<f64, String>`. If the division is successful (i.e., not dividing by zero), it returns `Ok(a / b)`. If division by zero is attempted, it returns an `Err` variant containing an error message. The `match` statement in the `main` function then handles the two possible cases, printing the result or the error message accordingly.

Combining Option and Result

In some cases, you may encounter situations where both `Option` and `Result` are necessary. In this example, we'll implement a simple program that calculates the area of a triangle using its base and height, which are stored in a database. We'll use the Option type to represent the possibility of a missing value in the database, and the Result type to handle errors during calculations.

// Simulated database access function
fn get_from_database(key: &str) -> Option<f64> {
    let database = vec![
        ("base", Some(4.0)),
        ("height", Some(6.0)),
    ];

    for (k, v) in database {
        if k == key {
            return v;
        }
    }
    None
}

fn calculate_triangle_area(base: Option<f64>, height: Option<f64>) -> Result<f64, String> {
    match (base, height) {
        (Some(b), Some(h)) => {
            if b <= 0.0 || h <= 0.0 {
                Err("Both base and height must be positive numbers.".to_string())
            } else {
                Ok(0.5 * b * h)
            }
        },
        (None, _) => Err("The base is missing.".to_string()),
        (_, None) => Err("The height is missing.".to_string()),
    }
}

fn main() {
    let base = get_from_database("base");
    let height = get_from_database("height");
    let area_result = calculate_triangle_area(base, height);

    match area_result {
        Ok(area) => println!("The area of the triangle is: {} square units.", area),
        Err(error_message) => println!("Error: {}", error_message),
    }
}


In this example, we simulate a database access function get_from_database that returns an Option<f64> value. The Option type is used because the requested value may not be present in the database.

The calculate_triangle_area function takes two Option<f64> values, base and height. Inside the function, we use a match expression to handle various cases: both base and height are present, one of them is missing, or both are missing. If both values are present and valid, we calculate the area and return Ok(0.5 * b * h). If one or both values are missing or invalid, we return an appropriate error message wrapped in Err.

In the main function, we fetch the base and height values from the simulated database and call calculate_triangle_area with these values. Finally, we handle the returned Result<f64, String> by either printing the calculated area or displaying an error message.

Rise In Logo

Rise together in web3