はじめに
OptionとResultはRustにおいて非常に重要な型です。
似たような型ですがちゃんと使い分けがあって頻繁に登場するため、これらの取り扱いに慣れておくとrustでのプログラミングが捗ります。
それでは見ていきましょう。
Option<T>型
概要
Option<T>
型は 取得できないかもしれない値 を表現する列挙型です。
値が無いことを示すNone
とあることを示すSome(T)
のどちらかをとります。
None
がエラーを表しているわけでは無いことに注意してください。None
は値がなかったことを示しているだけで、エラーとは限りません。
エラーを表したい場合は後述のResult<T,E>
を使う方がふさわしいでしょう。
pub enum Option<T> {
None,
Some(T),
}
他の言語でいうnullable typeやHaskellのMaybeと同じ使い方をします。
hashmapから値を取り出す時など、値の有無チェックと値の取得を同時に行いたい場合等に利用します。
fn get_value_bad(v: bool, result: &mut usize) -> bool {
if v {
*result = 100;
true
} else {
false
}
}
fn get_value_good(v: bool) -> Option<usize> {
if v {
Some(100)
} else {
None
}
}
fn main() {
// rustぽくない書き方
let mut result = 0;
if get_value_bad(true, &mut result) {
println!("success: {}", result);
} else {
println!("failure");
}
// rustぽい書き方
match get_value_good(true) {
Some(result) => println!("success: {}", result),
None => println!("failure"),
}
}
Optionの便利メソッド
fn main(){
// Some(v): 値がある場合
// None: 値が無い場合
let some_value: Option<usize> = Some(1);
let none_value: Option<usize> = None;
// Optionの中身はパターンマッチで取り出す
match some_value {
Some(v) => println!("some value = {}", v),
None => println!("none value"),
};
// Someの場合だけ処理したいならif letの方が便利
if let Some(v) = some_value {
println!("some value = {}", v);
}
// 比較もできる
// ただし、Option<T>のT型がEqやOrdトレイトを実装している必要がある
assert!(some_value != none_value);
// Option<T>には便利メソッドがいろいろある
// Some(v) or Noneを判定
assert!(some_value.is_some());
assert!(none_value.is_none());
// unwrap: (&self) -> T
// Someの場合は中身を返し、Noneの場合はpanicして終了
assert_eq!(some_value.unwrap(), 1);
// assert_eq!(none_value.unwrap(), 1); // panicする
// expect: (&self, &str) -> T
// unwrapと同じだが、panicするときにメッセージを指定できる
assert_eq!(some_value.expect("panic!"), 1);
// assert_eq!(none_value.expect("panic!"), 1); // panicする
// unwrap_or: (&self, T) -> T
// unwrapと同じだが、Noneの場合にpanicする代わりに別の値を返す
assert_eq!(some_value.unwrap_or(0), 1);
assert_eq!(none_value.unwrap_or(0), 0);
// unwrap_or_else: (&self, FnOnce() -> T) -> T
// unwrap_orと同じだが、Noneの場合の値をClosureで指定する
// 遅延評価したいときに使う
assert_eq!(some_value.unwrap_or_else(|| 0), 1);
assert_eq!(none_value.unwrap_or_else(|| 0), 0);
// map: (&self, FnOnce(T) -> U) -> Option<U>
// Someの場合は関数を適用し、Noneの場合はNoneまま返す
assert_eq!(some_value.map(|v| format!("value is {}", v)), Some("value is 1".to_string()));
assert_eq!(none_value.map(|v| format!("value is {}", v)), None);
// map_or: (&self, U, FnOnce(T) -> U) -> U
// unwrap_orのmap版
assert_eq!(some_value.map_or("value is default".to_string(), |v| format!("value is {}", v)), "value is 1".to_string());
assert_eq!(none_value.map_or("value is default".to_string(), |v| format!("value is {}", v)), "value is default".to_string());
// map_or_else: (&self, FnOnce() -> U, FnOnce(T) -> U) -> U
// unwrap_or_elseのmap版
assert_eq!(some_value.map_or_else(|| "value is default".to_string(), |v| format!("value is {}", v)), "value is 1".to_string());
assert_eq!(none_value.map_or_else(|| "value is default".to_string(), |v| format!("value is {}", v)), "value is default".to_string());
// 他にもいろいろあります!
// https://doc.rust-lang.org/std/option/enum.Option.html
}
Result<T,E>型
概要
Result<T,E>
は失敗するかもしれない処理の結果を表現する列挙型です。
Resultはrustコンパイラから特別扱いされており、無視するとwarinigが出ます。
Resultは例外がないrustにおける標準のエラーハンドリング方法です。エラーが発生する可能性がある場合は結果にResultを用いるようにしましょう。
enum Result<T, E> {
Ok(T),
Err(E),
}
他言語で例外を利用したり、エラーコードを返したいような場合に利用します。
Result型の戻り値を無視するとwarningが出るのでエラーハンドリングしていないことに気づけますし、パターンマッチで取り出せて例外より取り回しやすいのが良いです。
fn get_value_bad(v: bool, result: &mut usize) -> usize {
if v {
*result = 100;
0
} else {
1
}
}
fn get_value_good(v: bool) -> Result<usize,&'static str> {
if v {
Ok(100)
} else {
Err("error message")
}
}
fn main() {
// エラーコードを返す書き方
// 利用者が戻り値を無視できるのでよく無い
let mut result = 0;
if get_value_bad(true, &mut result) == 0 {
println!("success: {}", result);
} else {
println!("failure");
}
// rustぽい書き方
match get_value_good(true) {
Ok(result) => println!("success: {}", result),
Err(msg) => println!("failure: {}", msg),
}
}
Resultの便利メソッド
fn main(){
// Ok(v): 処理に成功した場合。返す値が無い場合はUnit型()を返すとよい
// Err(e): 処理に失敗した場合。
let ok_value: Result<usize, &'static str> = Ok(1);
let er_value: Result<usize, &'static str> = Err("error message");
// Resultの中身はパターンマッチで取り出す
match ok_value {
Ok(v) => println!("ok value = {}", v),
Err(e) => println!("err value = {}", e),
};
// どちらか片方の場合だけ処理したいならif letが便利
if let Ok(v) = ok_value {
println!("ok value = {}", v);
}
if let Err(e) = er_value {
println!("err value = {}", e);
}
// 比較もできる
// ただし、Reulst<T,E>のTとE両方がEqやOrdトレイトを実装している必要がある
assert!(ok_value != er_value);
// Result<T,E>には便利メソッドがいろいろある
// Ok(v) or Err(e)を判定
assert!(ok_value.is_ok());
assert!(er_value.is_err());
// ok: (&self)
// Option型に変換する。Okの場合はSomeに、Errの場合はNoneになる
assert_eq!(ok_value.ok(), Some(1));
assert_eq!(er_value.ok(), None);
// err: (&self)
// okメソッドの逆バージョン。Okの場合はNoneに、Errの場合はSomeになる
assert_eq!(ok_value.err(), None);
assert_eq!(er_value.err(), Some("error message"));
// unwrap<T,E>: (&self) -> T
// Okの場合は中身を返し、Errの場合はpanicする
assert_eq!(ok_value.unwrap(), 1);
// assert_eq!(er_value.unwrap(), 1); // panic
// expect<T,E>: (&self, &str) -> T
// unwrapと同じだが、panic時のエラーメッセージを指定できる
assert_eq!(ok_value.expect("panic"), 1);
// assert_eq!(er_value.expect("panic"), 1); // panic
// unwrap_or<T,E>: (&self, T) -> T
// unwrapと同じだが、panicする代わりに別の値を返す
assert_eq!(ok_value.unwrap_or(0), 1);
assert_eq!(er_value.unwrap_or(0), 0);
// unwrap_or_else<T,E>: (&self, FnOnce(E) -> T) -> T
// unwrap_orと同じだが、Errの場合の値をClosureで指定する
// 遅延評価したいときに使う
// Optionと違ってErrの値を引数に取るので注意
assert_eq!(ok_value.unwrap_or_else(|_e| 0), 1);
assert_eq!(er_value.unwrap_or_else(|_e| 0), 0);
// unwrap_err<T,E>: (&self) -> E
// expect_err<T,E>: (&self, &str) -> E
// unwrap, expectのokとerrを逆にしたバージョン。okだとpanicする
// assert_eq!(ok_value.unwrap_err(), "error message"); // panic
assert_eq!(er_value.unwrap_err(), "error message");
// assert_eq!(ok_value.expect_err("panic"), "error message"); // panic
assert_eq!(er_value.expect_err("panic"), "error message");
// map<T,E,U>: (&self, FnOnce(T) -> U) -> Result<U,E>
// map_err<T,E,U>: (&self, FnOnce(E) -> U) -> Result<T,U>
// Resultの中身を関数を適用してmapする
// Okの場合に適用するmapとErrの場合に適用するmap_errがある
assert_eq!(ok_value.map(|v| format!("{}", v)), Ok("1".to_string()));
assert_eq!(er_value.map(|v| format!("{}", v)), Err("error message"));
assert_eq!(ok_value.map_err(|s| s.len()), Ok(1));
assert_eq!(er_value.map_err(|s| s.len()), Err(13));
}