06_Cairo 中の Option(特殊な Enum)#
Rust と同様に、Cairo には空のシステムレベルの変数や属性を表す Null のようなものはありません。なぜなら、空の値を非空の値として扱ったり、非空の値を空の値として扱ったりするエラーが発生する可能性があるためです。
このエラーをよりよく理解するために、Golang の例を挙げます:
Golang の Map を使用する際に、"nil の Map からデータを読み取る" というエラーがよく発生します。これは、Map が実行時に nil である可能性があるためです。nil は Golang では空の値を表します。
Cairo では、このようなエラーは発生しません。なぜなら、Cairo コンパイラはコンパイル時にこのようなエラーを検出できるからです。この効果を実現するための理由は、Cairo には空のシステムレベルの変数や属性を表す Null のようなものがなく、Cairo は非空の判定(Option)を実装するために特殊な Enum 型を使用しているからです。
Option の基本的な使用法#
標準ライブラリでは Option は次のように定義されています。
enum Option<T> {
Some: T,
None: (),
}
実際のコーディングでは、次のように Option 型の変数を定義することができます:
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);
}
Option の Some メンバはジェネリックなので、任意の型を格納することができます。上記の例では、短い文字列を Some に格納しています。また、注意点として、None を使用する場合、引数は空にすることはできず、unit 型 ()
を渡す必要があります。unit 型は Cairo の特殊な型であり、その位置のコードがコンパイル時の空の状態であることを保証するために使用されます。
Rust との類似点#
Rust との共通点:
- None を使用する場合、Option の中の型を指定する必要があります。例えば、
let some_number: Option<felt252> = Option::None(());
のように、None では Option の中に何の型が入っているかをコンパイラが判断することができません。
Rust との相違点:
- None と Some は直接的にグローバルに使用することはできません。
他の型の変数と Option を混在させることはできません#
use option::OptionTrait;
fn main() {
let x: u8 = 5_u8;
let y: Option<u8> = Option::Some(5_u8);
let sum = x + y;
}
// 以下のエラーが発生します:
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;
^
Option 変数 y には u8 の値が入っていますが、y と x の型は異なるため、2 つを足すことはできません。
Cairo での非空判定#
Cairo では、すべての型の変数は非空です。コンパイラは変数が常に空でないことを保証します。つまり、Cairo のコードを書く際には、自分の変数が実行中に空の値になっているかどうかを心配する必要はありません。唯一、空の値であるかどうかを考慮する必要があるのは、Option 変数を使用する場合です。
言い換えると、Option だけが空の値(None)にアクセスできます。Option の実際の使用例は、マッチングパターンの制御を使用して確認することができます。
ソースコードの解析#
Option のコアライブラリのソースコード:https://github.com/starkware-libs/cairo/blob/main/corelib/src/option.cairo
4 つのメンバ関数#
4 つのメンバ関数があります:
/// Optionに値がある場合はその値を返し、ない場合はエラーをスローします
fn expect(self: Option<T>, err: felt252) -> T;
/// Optionに値がある場合はその値を返し、ない場合もエラーをスローしますが、このエラーはカスタムエラーではありません
fn unwrap(self: Option<T>) -> T;
/// Optionに値がある場合はtrueを返します
fn is_some(self: @Option<T>) -> bool;
/// Optionに値がない場合はfalseを返します
fn is_none(self: @Option<T>) -> bool;