06_Cairo Option (Special Enum)#
Like Rust, Cairo also does not have a system-level variable or attribute that represents null or empty. This is because it is easy to make mistakes 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 may 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 attribute 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, you can define variables of type Option like this:
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 put a short string into Some. It is also important to note that if None is used, the parameter cannot be empty. Instead, you need to pass the unit type ()
. The unit type is a special type in Cairo that ensures that the code in its position is not compiled (empty state during compilation).
Similarities and Differences with Rust#
Similarities with Rust:
- When using None, you need to specify the type in Option, just like
let some_number: Option<felt252> = Option::None(());
. The compiler cannot determine the type inside Option from None.
Differences with Rust:
- None and Some cannot be used directly globally.
Mixing variables of other types with Option 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, y and x have different types, so they cannot be added together.
Non-null Checks in Cairo#
In Cairo, all variables of any type 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, you don't have to worry about forgetting to check if your variables are null during runtime. The only place where you need to consider null values is when using Option variables.
In other words, only Option can have the state of a null value (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 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 has no value
fn is_none(self: @Option<T>) -> bool;