DEV Community

Cover image for [Rust Guide] 6.2. The Option Enum
SomeB1oody
SomeB1oody

Posted on

[Rust Guide] 6.2. The Option Enum

6.2.1. What Is the Option Enum?

It is defined in the standard library and included in the prelude (the pre-imported module). It is used to describe a scenario where:
a value may exist, and if so, what data type it has; or it may simply not exist.

6.2.2. Rust Has No Null

In most other languages, there is a Null value, which represents no value.

In those languages, a variable can be in two states:

  • Null (Null)
  • Non-null

Null’s inventor, Tony Hoare, said in his 2009 talk “Null References: The Billion Dollar Mistake”:

I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

The problem with Null is very obvious, and even its inventor does not think it is a good thing. For example, if a variable is of type string and needs to be concatenated with another string, but the variable is actually Null, then an error will occur during concatenation. For Java users, the most common error is NullPointerException. In one sentence, when you try to use a Null value as if it were a non-Null value, some kind of error will occur.

Therefore, Rust does not provide Null. However, for the concept that Null is trying to express—namely, a value that is currently invalid or does not exist for some reason—Rust provides a similar enum called Option<T>.

6.2.3. Option<T>

It is defined in the standard library like this:

enum Option<T>{
    Some(T),
    None,
}
Enter fullscreen mode Exit fullscreen mode
  • The Some variant can carry some data, and its data type is T. <T> is actually a generic parameter (covered later).
  • None is the other variant, but it does not carry any data, because it represents the case where a value does not exist.

Because it is included in the Prelude, you can use Option<T>, Some(T), and None directly.

Look at an example:

fn main(){  
    let some_number = Some(5);  
    let some_char = Some('e');  

    let absent_number: Option<i32> = None;  
}
Enter fullscreen mode Exit fullscreen mode
  • For the first two statements, the values are written inside the parentheses, so the Rust compiler can infer their data types. For example, some_number has type Option<i32>, and some_char has type Option<char>. Of course, you can also write the type explicitly, but it is unnecessary unless you want to force a specific type.
  • For the last statement, the assigned value is the None variant. The compiler cannot infer from None what type T in Option<T> should be, so you need to declare the concrete type explicitly. That is why Option<i32> is written here.

In this example, the first two variables are valid values, while the last variable does not contain a valid value.

6.2.4. The Advantages of Option<T>

  • In Rust, Option<T> and T (T can be any data type) are different types. You cannot treat Option<T> as T.
  • If you want to use the T inside Option<T>, you must first convert it to T. This prevents programmers from ignoring the possibility of null values and directly operating on variables that may be empty. Rust’s Option<T> design forces developers to handle these cases explicitly. For example, in C#, if you write string a = null; and then string b = a + "12345";, and you do not check whether a is null (or ignore the possibility that a is null), an error will occur. In Rust, as long as a value’s type is not Option<T>, that value is definitely not null.

For example:

fn main(){  
    let x: i8 = 5;  
    let y: Option<i8> = Some(5);  

    let sum = x + y;  
}
Enter fullscreen mode Exit fullscreen mode

If you run this code, the compiler will report an error:

error[E0277]: cannot add `Option<i8>` to `i8`
 --> src/main.rs:5:17
  |
5 |     let sum = x + y;
  |                 ^ no implementation for `i8 + Option<i8>`
  |
  = help: the trait `Add<Option<i8>>` is not implemented for `i8`
  = help: the following other types implement trait `Add<Rhs>`:
            `&i8` implements `Add<i8>`
            `&i8` implements `Add`
            `i8` implements `Add<&i8>`
            `i8` implements `Add`
Enter fullscreen mode Exit fullscreen mode

The error means that the types Option<i8> and i8 cannot be added together because they are not the same type.

So how can we make x and y add together? It is simple: convert y from Option<i8> to i8:

fn main() {  
    let x: i8 = 5;  
    let y: Option<i8> = Some(5);  

    let sum = match y {  
        Some(value) => x + value, // If y is Some, unwrap and add  
        None => x,               // If y is None, return x    
    };  
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)