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)
}最後に
公式ドキュメントの情報は豊富ですが、やはりやってみないとわからないこともしばしば。
気になったことは実装して動かして見るに限りますね。
本記事では紹介していないメソッドもたくさんあり、新バージョンに向けた新しいメソッドもそこそこありますので、随時更新して情報を増やしていくつもりです。
もっと詳細を知りたい方や余力がある方はぜひ公式ドキュメントも確認してみてください。



