Rust is SERIOUS about safety and control over memory. Option
type – an essential tool in Rust's arsenal that keeps your code safe and sound. Option
is a fundamental type in Rust and is widely used for error handling, optional values, and more. You would often encounter with functions in Rust that return Option
type. It encourages the safe handling of potentially absent or invalid values and helps prevent dreaded null pointer errors that plague C++.
Introducing Option
Option
is an enumeration (enum) type that represents either a value or the absence of a value. It can either be "Some" or "None," helping you navigate the potentially missing or invalid values. It is defined by the standard library as follows:
enum Option<T> {
Some(T),
None,
}
The Option enum is so useful that it’s even included in the prelude. In other words, if a value has a type that isn’t an Option, you can safely assume that the value isn’t null.
Option Variants
The option
has two variants:
Some(T)
: Represents a value of typeT
. <T> is a generic type parameter. It indicates that a value is present.None
: Represents the absence of a value.
Some and None are used directly without the Option:: prefix as they are included in the prelude as well.
The Null-less Rust Coast
Rust is free of the "null" feature that many other languages have. Null is a value that means there is no value there. In languages with null, variables can always be in one of two states: null or not-null. The problem? If you try to treat null as not-null, you’ll get an error and since null or not-null is a common property, it's easier to fall into this.
Sailing with Option
In Rust, Option
is the ship, safeguarding you from these treacherous waters. Let's embark on this adventure with a simple example: Here the fun1
function returns an Option<f64>
which means it can either return Some(f64)
or None
.
fn fun1()->Option<f64>{
let x = 0.0;
if x ==0.0{
return Some(x);
}
else{
return None;
}
}
fn main(){
let x = fun1();
let y = 1.0;
let sum = x+y;
}
error[E0369]: cannot add `{float}` to `Option<{float}>`
--> src/main.rs:4:16
|
4 | let sum = x+y;
| -^- {float}
| |
| Option<{float}>
The expression x + y
tries to add an Option<f64>
(x
) to a f64
(y
). This is not allowed because Rust's type system enforces safety, and it doesn't allow adding an Option
to a regular value directly. You would need to handle the Option
using match statements or other methods to extract the value from Some
(if it exists) and then perform the addition.
The Option
has a large number of methods that are useful in a variety of situations like this. You can use methods like unwrap
or unwrap_or
to handle the Option
and get its value.
Methods to work with Option
The Option
enum provides several methods and associated functions that allow you to work with optional values (values that can be either Some(value)
or None
). Here are some of the commonly used methods for Option
:
unwrap()
: This method extracts the value from aSome
variant and panics if the variant isNone
. It should be used with caution, as it can lead to a panic.unwrap_or(default)
: Extracts the value from aSome
variant or returns a default value if the variant isNone
. It provides a safer alternative tounwrap()
.unwrap_or_else(fn)
: Similar tounwrap_or
, it takes a closure that computes and returns the default value if needed. This allows for lazy evaluation of the default value.expect(msg)
: Similar tounwrap()
, but it allows you to provide a custom error message that will be displayed if the variant isNone
.is_some()
: Returnstrue
if the variant isSome
, indicate the presence of a value.is_none()
: Returnstrue
if the variant isNone
, indicating the absence of a value.map(fn)
: Applies a function to the inner value if it'sSome
and return a newOption
containing the result. If it'sNone
, it returnsNone
.map_or(default, fn)
: Applies a function to the inner value if it'sSome
, return the result. If it'sNone
, it returns the provided default value.
fn main(){
let x = fun1();
println!("{}",x.unwrap_or(10.0));
println!("{}",x.unwrap_or_else(|| {fun2()}));
println!("{}",x.expect(" It has None"));
println!("{:?}",x.is_some());
}
fn fun2()->f64{
100.0
}
fn fun1()->Option<f64>{
let x = 0.0;
if x ==0.0{
return Some(x);
}
else{
return None;
}
}
----------------------------Output---------------------
0
0
0
true
Pattern Matching with Option
fn main() {
let x = fun1();
match x {
Some(x) => println!("The value is: {}", value),
None => println!("No value found"),
}
}
In the code above, we use match
to extract and handle the value inside the Option
. When Some
, we print the value; when None
, we indicate that no value was found.
Another example of Deserialize and Serialize JSON
Let's see an example of JSON. You are working with JSON where a few attributes are optional. In the below example, let's assume style is optional, you could get JSON as input where style is absent.
{
"current_price": 100.0
"style":"American"
}
To handle this case, you could define struct as below:
#[derive(Clone,Debug,Deserialize,Serialize)]
pub struct Contract {
pub current_price: f64,
pub style: Option<String>
}
pub struct EquityOption {
pub current_price: f64,
pub style: Option<String>,
}
let mut contents = String::new();
file.read_to_string(&mut contents).expect("Failed to read JSON file");
let data: utils::Contract = serde_json::from_str(&contents)
.expect("Failed to deserialize JSON")
let mut contract = EquityOption {
current_price: data.current_price,
style: Option::from(data.style.as_ref().unwrap_or(&default_style))
error[E0308]: mismatched types
style : Option::from(data.style.as_ref().unwrap_or(&"European".to_string()))
| ^^^^^^^^^^^^^^^^^^^^^^ expected `Option<String>`, found `Option<&String>`
|
= note: expected enum `Option<String>`
found enum `Option<&String>`
Option::from
is used to convert a value of one type into an Option
of that type.
We used Option<String>
in our equity struct that is an Option
that can either hold a Some(String)
value. The String
inside Option<String>
is owned, meaning we have exclusive ownership of this String.
The above code gives an error because we get the Option<&String>
type. Option<&String>
is an Option
that can hold a reference to a String
(&String
) or None
. The &String
inside Option<&String>
is a borrowed reference, meaning it does not have ownership of the data, and it cannot be modified or moved. It points to an existing String
owned elsewhere.
Either we can change the definition of our struct and use Option<&String>
then we have to make sure about the borrowing and life of the referred string. Or we can clone the string as per our use case.
style : Option::from(String::from(data.style.as_ref()
.unwrap_or(&"European".to_string())))
.unwrap_or(&"European".to_string())
is used to provide a default value in case the Option
is None
. If the data.style
is Some(String)
, this part is ignored. If the data.style
was None
, it creates a new String
with the value "European"
and returns a reference to it. Note unwrap_or
expect &String
hence we return a reference &
.
Here is youtube video discuss about the Option<&T> and &Option<T>
Conclusion
In conclusion, you typically use Option
in Rust represents optional or nullable value and wants to avoid null pointer errors and ensure safer code. You most often use when:
You expect a function to return a value but it might fail or return nothing, you use
Option
to handle the result.You have a variable that might be initialized with a valid value or remain uninitialized, you use
Option
to represent its state.