06_Cairo Option (Special Enum)#
Like Rust, Cairo also does not have a system-level variable or property that represents null or empty. This is because it is easy to make errors such as treating a null value as a non-null value or vice versa.
To better understand this error, let's take an example from Golang:
When using a Map in Golang, it is common to encounter an error of "reading data from a Map with a nil state" because the Map can be nil at runtime. In Golang, nil represents a null value.
Such errors do not occur in Cairo because the Cairo compiler can detect these errors during compilation. The reason for this is that Cairo does not have a system-level variable or property that represents null or empty. Instead, Cairo uses a special Enum type to implement non-null checks (Option).
Basic Usage of Option#
Option is defined in the standard library as follows:
enum Option<T> {
Some: T,
None: (),
}
In actual coding, Option variables can be defined as follows:
use option::OptionTrait;
fn main(){
let some_char = Option::Some('e');
let some_number: Option<felt252> = Option::None(());
let absent_number: Option<u32> = Option::Some(8_u32);
}
The Some member of Option is generic, so it can hold any type. In the example above, we have stored a short string in Some. It is also important to note that when using None, the parameter cannot be empty. Instead, the unit type ()
needs to be passed. The unit type is a special type in Cairo that ensures that the code at its location is not compiled (empty state during compilation).
Similarities and Differences with Rust#
Similarities with Rust:
- When using None, the type inside Option needs to be specified, as shown in
let some_number: Option<felt252> = Option::None(());
. This is because the compiler cannot determine the type inside Option from None.
Differences with Rust:
- None and Some cannot be used globally.
Mixing Option with Variables of Other Types is Not Allowed#
use option::OptionTrait;
fn main() {
let x: u8 = 5_u8;
let y: Option<u8> = Option::Some(5_u8);
let sum = x + y;
}
// This will result in the following error:
error: Unexpected argument type. Expected: "core::integer::u8", found: "core::option::Option::<core::integer::u8>".
--> h04_enum_option.cairo:11:19
let sum = x + y;
^
Although the Option variable y contains a value of type u8, the types of y and x are different, so they cannot be added together.
Non-Null Checks in Cairo#
All variables in Cairo are non-null, and the compiler ensures that variables are never empty at any time, just like the x variable mentioned above. This means that when writing Cairo code, there is no need to worry about forgetting to check if a variable is null during runtime. The only place where the possibility of null needs to be considered is when using Option variables.
In other words, only Option can have the state of null (None). We can see practical examples of Option usage through the control pattern of match.
Source Code Analysis#
Source code of the Option core library: https://github.com/starkware-libs/cairo/blob/main/corelib/src/option.cairo
4 Member Functions#
There are 4 member functions:
/// Checks if Option has a value, returns the value if it does; if not, throws an error with err
fn expect(self: Option<T>, err: felt252) -> T;
/// Checks if Option has a value, returns the value if it does; if not, throws a default error
fn unwrap(self: Option<T>) -> T;
/// Returns true if Option has a value
fn is_some(self: @Option<T>) -> bool;
/// Returns false if Option does not have a value
fn is_none(self: @Option<T>) -> bool;