RustにはOptionやResultのように他の言語ではあまり見ない独特な型があります。
最近Rust学習を始めた身ではありますが、OptionやResultといった型はRustプログラミングにおいて重要で、使いこなせるかどうかがRustを使用したコーディングのキーポイントとなるとも思います。
Rustは公式ドキュメントが豊富で、公式ドキュメントを読めばきちんと理解できるようになっていますが、現状は少し敷居が高い(読むのが疲れる)と感じます。
そこで自身の勉強のアウトプット・整理と同時に、同じようにRustを勉強している方や「公式ドキュメントはちょっと、、」というような方が気楽に見れるようにサンプルコードをメインに簡単にまとめてみました。
今回はOption型について見ていきましょう。
- Option型の基本
- Option型の便利なメソッドとその使い方
基本
Option<T>
は次のいずれかの値を示す型です。
Some(T)
:なんらかの値を含むものNone
:値を含まないもの(Nullではない)
これだとわかりにくいかもしれませんが、Option<T>
自体の実装はとてもシンプルです。
pub enum Option<T> {
None,
Some(T),
}
要するにただの列挙型(Enum)です。
値を含む場合はSome(T)
、値がない場合はNone
というような単純な型となっています。
使い方は以下のようになります。
fn sample() {
let some: Option<i32> = Some(1);
println!("{:?}", some);
// -> Some(1)
let none: Option<i32> = None;
println!("{:?}", none);
// -> None
}
便利なメソッド
公式ドキュメントからOptionで特に使いそうなメソッドをリストアップしてみました。
サンプルコードと注意点も簡単に記載しています。
unwrap
Some(T)
の中身を取得する。None
の場合はpanic
を引き起こす。
所有権の移動が発生するので同じものに対して複数回は行えない。
fn sample() {
let some: Option<String> = Some("dummy".to_string());
println!("{}", some.unwrap()); // -> "dummy"
println!("{}", some.unwrap()); // 上で所有権が移動済なのでコンパイルエラーとなる
let none: Option<String> = None;
println!("{}", none.unwrap()); // Noneの場合はpanicとなる
}
公式ドキュメントでは基本的に使用は推奨されておらず、Noneケースを処理できるような方法が望ましいとされている。(unwrap_orなどを使うなど)
unwrap_or
unwrap
の派生形。
None
に対して実行した場合は引数に指定した値が返却される。
fn sample() {
let some: Option<String> = Some("dummy".to_string());
println!("{}", some.unwrap_or("or value".to_string()));
// -> "dummy"
println!("{}", some.unwrap_or("or value".to_string()));
// -> unwrapと同様に所有権の移動が発生するためコンパイルエラーとなる
let none: Option<String> = None;
println!("{}", none.unwrap_or("or value".to_string()));
// -> "or value"
}
unwrap_or_else
unwrap_or
と同様だが、引数には関数を渡す。
None
の場合に引数に渡した関数が実行されその結果が返却される。
fn sample() {
let some: Option<String> = Some("dummy".to_string());
println!("{}", some.unwrap_or_else(|| "or value".to_string()));
// -> "dummy"
println!("{}", some.unwrap_or_else(|| "or value".to_string()));
// -> unwrapと同様に所有権の移動が発生するためコンパイルエラーとなる
let none: Option<String> = None;
println!("{}", none.unwrap_or_else(|| "or value".to_string()));
// -> "or value"
}
unwrap_or_default
unwrap_or
と同様だが、引数指定なしで使用可能。
None
の場合にOption<T>
におけるT
型のデフォルト値を返すことができる。
fn sample() {
let some: Option<String> = Some("dummy".to_string());
println!("{}", some.unwrap_or_default());
// -> "dummy"
println!("{}", some.unwrap_or_default());
// -> unwrapと同様に所有権の移動が発生するためコンパイルエラーとなる
let none: Option<String> = None;
println!("{}", none.unwrap_or_default());
// -> "" (Stringのデフォルト値)
}
expect
Some(T)
の中身を取得する。None
の場合は引数に指定した文字列でpanic
を発生。
所有権の移動が発生するので同じ物に対して複数回は行えない。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.expect("no error"));
// -> "dummy"
println!("{:?}", some.expect("compile error"));
// 上で所有権が移動済なのでコンパイルエラーとなる
let none: Option<String> = None;
println!("{:?}", none.expect("panic!!!")); // Noneの場合はpanicとなる
}
as_ref
Option<T>
をOption<&T>
にして参照する。
unwrap
やmap
などのメソッドはOption<T>
の中身を消費(所有権を移動)するメソッドだが、as_ref
を使用して参照を挟むことで消費しないようにすることができる。
fn sample() {
let some: Option<String> = Some("dummy".to_string());
println!("{:?}", some.unwrap()); // unwrapで所有権が移動する
println!("{:?}", some.unwrap()); // 上の行で所有権が移動済なのでコンパイルエラーとなる
let some2: Option<String> = Some("dummy".to_string());
println!("{:?}", some2.as_ref().unwrap()); // as_refをはさむことで所有権の移動が発生しない
println!("{:?}", some2.unwrap()); // この時点ではまだ所有権は移動していないのでコンパイルエラーとならない
println!("{:?}", some2.as_ref().unwrap()); // 上の行で所有権が移動済なので参照も作れない(コンパイルエラー)
}
map
Option<T>
をOption<U>
にする。
引数に渡した関数で変換・整形処理を行い、その結果としてOption<U>
を返却する。
None
に対して実行した場合、引数に渡した関数は実行されないことに注意。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.map(|s| { s.len() }));
// -> Some(5)
let none: Option<String> = None;
println!("{:?}", none.map(|s| { s.len() }));
// -> None(このときmapに渡した関数は実行されず、ただNoneという結果となる)
}
map_or
map
にデフォルト値がついたもの。
戻り値がOption
型ではなく、中身のT
型となることに注意。
map
と同様に、None
の場合は引数に渡した関数は実行されないことに注意。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.map_or(100, |s| { s.len() }));
// -> 5
let none: Option<String> = None;
println!("{:?}", none.map_or(100, |s| { s.len() }));
// -> 100 (Noneのためmap_orに渡した関数は実行されない)
}
map_or_else
map_or
のデフォルト値指定部分が関数になったもの。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.map_or_else(|| 100, |s| { s.len() },));
// -> 5
let none: Option<String> = None;
println!("{:?}", none.map_or_else(|| 100, |s| s.len()));
// -> 100 (Noneのためmap_or_elseに渡した第2引数の関数は実行されない)
}
ok_or
Option<T>
をResult<T,E>
に変換する。
Some(T)
の場合はOk(T)
に、None
の場合はErr(E)
にそれぞれ変換される。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.ok_or(100));
// -> Ok("dummy")
let none: Option<String> = None;
println!("{:?}", none.ok_or(100));
// -> Err(100)
}
ok_or_else
ok_or
で指定する引数が関数になったもの。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.ok_or_else(|| 100));
// -> Ok("dummy")
let none: Option<String> = None;
println!("{:?}", none.ok_or_else(|| 100));
// -> Err(100)
}
and / or / xor
and
,or
,xor
という名前ですが、真理値表のように考えると混乱するかも。
ドキュメントにあるように以下のように動作することに注意。
and | Some(T) の場合は引数に渡した値を、None の場合はNoneを返す |
or | Some(T) の場合はSome(T) 自身を、None の場合は引数に渡した値を返す |
xor | どちらか一方のみがSome(T) のときのみSome(T) を返す |
fn sample() {
let some: Option<&str> = Some("some");
let another_some: Option<&str> = Some("another_some");
let none: Option<&str> = None;
let another_none: Option<&str> = None;
// and
println!("{:?}", some.and(another_some)); // -> Some("another_some")
println!("{:?}", some.and(none)); // -> None
println!("{:?}", none.and(some)); // -> None
println!("{:?}", none.and(another_none)); // -> None
// or
println!("{:?}", some.or(another_some)); // -> Some("some")
println!("{:?}", some.or(none)); // -> Some("some")
println!("{:?}", none.or(some)); // -> Some("some")
println!("{:?}", none.or(another_none)); // -> None
// xor
println!("{:?}", some.xor(another_some)); // -> None
println!("{:?}", some.xor(none)); // -> Some("some")
println!("{:?}", none.xor(some)); // -> Some("some")
println!("{:?}", none.xor(another_none)); // -> None
}
and_then / or_else
and
, or
の引数が関数になったもの。
fn sample() {
let some: Option<&str> = Some("some");
let another_some: Option<&str> = Some("another_some");
let none: Option<&str> = None;
let another_none: Option<&str> = None;
// and_then
println!("{:?}", some.and_then(|_| another_some)); // -> Some("another_some")
println!("{:?}", some.and_then(|_| none)); // -> None
println!("{:?}", none.and_then(|_| some)); // -> None
println!("{:?}", none.and_then(|_| another_none)); // -> None
// or_else
println!("{:?}", some.or_else(|| another_some)); // -> Some("some")
println!("{:?}", some.or_else(|| none)); // -> Some("some")
println!("{:?}", none.or_else(|| some)); // -> Some("some")
println!("{:?}", none.or_else(|| another_none)); // -> None
}
filter
引数に渡した関数の結果がfalse
の場合はNone
を返却する。
Option
でチェーンにするときに活用できif
文の削減につながる。
fn sample() {
let some = Some("dummy".to_string());
println!("{:?}", some.as_ref().filter(|v| v.len() == 5));
// -> Ok("dummy")
println!("{:?}", some.as_ref().filter(|v| v.len() == 2));
// -> None
let none: Option<String> = None;
println!("{:?}", none.filter(|v| v.len() == 5));
// -> None
}
get_or_insert
Option<T>
の値がNone
の場合に引数に渡した値を含めたSome(T)
を返す。
mut Option<T>
な値に対して動作することに注意(get_or_insert
を実行した変数の中身を変更する)
fn sample() {
let mut some = Some("dummy".to_string());
println!("{:?}", some.get_or_insert("another".to_string()));
// -> "dummy"
let mut none: Option<String> = None;
println!("{:?}", none.get_or_insert("another".to_string()));
// -> "another"
println!("{:?}", none);
// -> Some("another") ※ 変数の値が変わっていることに注意
}
get_or_insert_with
get_or_insert
の引数が関数になったもの。
fn sample() {
let mut some = Some("dummy".to_string());
println!("{:?}", some.get_or_insert_with(|| "another".to_string()));
// -> "dummy"
let mut none: Option<String> = None;
println!("{:?}", none.get_or_insert_with(|| "another".to_string()));
// -> "another"
println!("{:?}", none);
// -> Some("another") ※ noneの値が変わっていることに注意
}
transpose
Option<Result<T,E>>
をResult<Option<T>,E>
に変換する。
fn sample() {
type Sample = Option<Result<i32, i32>>;
let ok_some: Sample = Some(Ok(1));
println!("{:?}", ok_some); // -> Some(Ok(1))
println!("{:?}", ok_some.transpose()); // -> Ok(Some(1))
let err_some: Sample = Some(Err(1));
println!("{:?}", err_some); // -> Some(Err(1))
println!("{:?}", err_some.transpose()); // -> Err(1)
let none: Sample = None;
println!("{:?}", none); // -> None
println!("{:?}", none.transpose()); // -> Ok(None)
}
最後に
公式ドキュメントの情報は豊富ですが、やはりやってみないとわからないこともしばしば。
気になったことは実装して動かして見るに限りますね。
本記事では紹介していないメソッドもたくさんあり、新バージョンに向けた新しいメソッドもそこそこありますので、随時更新して情報を増やしていくつもりです。
もっと詳細を知りたい方や余力がある方はぜひ公式ドキュメントも確認してみてください。