概要
Rustを学んでいて最初に??と思ったのがよく出てくるOptionとResultとそれぞれのメソッドです。
なので、自分なりに使い方を調べて簡単にまとめたものです。
ドキュメント
Option
pub enum Option<T> {
None,
Some(T),
}
Result
pub enum Result<T, E> {
Ok(T),
Err(E),
}
実行例
OptionとResultはよく似ているので両方を併記します。
データの生成
let o1: Option<i32> = Some(5);
println!("o1 value: {:?}", o1);
let o2: Option<i32> = None;
println!("o2 value: {:?}", o2);
let r1: Result<i32, String> = Ok(12);
println!("r1 value: {:?}", r1);
let r2: Result<i32, String> = Err("Error occurred".into());
println!("r2 value: {:?}", r2);
-
Option<T>
の型Tがi32のでSome
内の値はi32の型でなければならない -
Option<T>
のもう一つの値はNone
である -
Result<T, E>
の型Tが i32 なのでOk
内の値は i32 の型でなければならない- 良く見るのは型Tが () のときで、このときは
Ok(())
となる
- 良く見るのは型Tが () のときで、このときは
-
Result<T, E>
の型Eが String なのでErr
内の値は String の型でなければならない
o1 value: Some(5)
o2 value: None
r1 value: Ok(12)
r2 value: Err("Error occurred")
unwrap, expect
これはSome
やOk
内の値を取り出すが、None
やErr
のときはpanicになる
let o1: Option<i32> = Some(5);
let o1_val: i32 = o1.unwrap();
println!("o1 value: {}", o1_val);
let r1: Result<i32, String> = Ok(12);
let r1_val: i32 = r1.unwrap();
println!("r1 value: {}", r1_val);
let o2: Option<i32> = Some(7);
let o2_val: i32 = o2.expect("o2 value is missing");
println!("o2 value: {}", o2_val);
let r2: Result<i32, String> = Ok(18);
let r2_val: i32 = r2.expect("r2 value is missing");
println!("r2 value: {}", r2_val);
o1 value: 5
r1 value: 12
o2 value: 7
r2 value: 18
値がちゃんと取り出せています。
次はpanicになる例です。
let o1: Option<i32> = None;
let o1_val: i32 = o1.unwrap();
println!("o1 value: {}", o1_val);
thread 'main' panicked at src/main.rs:34:26:
called `Option::unwrap()` on a `None` value
println!
は実行されず2行目でpanicになっています。
let o1: Option<i32> = None;
let o1_val: i32 = o1.expect("o1 value is missing");
println!("o1 value: {}", o1_val);
thread 'main' panicked at src/main.rs:48:26:
o1 value is missing
let r1: Result<i32, String> = Err("Error occurred".into());
let r1_val: i32 = r1.unwrap();
println!("r1 value: {}", r1_val);
thread 'main' panicked at src/main.rs:40:26:
called `Result::unwrap()` on an `Err` value: "Error occurred"
let r1: Result<i32, String> = Err("Error occurred".into());
let r1_val: i32 = r1.expect("r1 value is missing");
println!("r1 value: {}", r1_val);
thread 'main' panicked at src/main.rs:61:26:
r1 value is missing: "Error occurred"
次にexpect
の処理をmatch
を使って表すと次のようになっている。
let o1: Option<i32> = Some(5);
//let o1_val: i32 = o1.expect("o1 value is missing");
let o1_val: i32 = match o1 {
Some(val) => val,
None => panic!("o1 value is missing"),
};
println!("o1 value: {}", o1_val);
let r1: Result<i32, String> = Err("Error occurred".into());
//let r1_val: i32 = r1.expect("r1 value is missing");
let r1_val: i32 = match r1 {
Ok(val) => val,
Err(err) => panic!("r1 value is missing: \"{}\"", err),
};
println!("r1 value: {}", r1_val);
o1 value: 5
thread 'main' panicked at src/main.rs:77:21:
r1 value is missing: "Error occurred"
Option
とResult
はenumなのでmatch
文を使えばほとんどのことが出来ますが、それを簡単なメソッドでどう表現するだけだと思います。
?演算子
fn main() -> Result<(), String> {
println!("enter main");
let rs: u32 = sub_func()?;
println!("sub_func returned: {}", rs);
Ok(())
}
fn sub_func() -> Result<u32, String> {
println!("enter sub_func");
Ok(10)
}
enter main
enter sub_func
sub_func returned: 10
sub_func done
上記の例では戻り値がOk
だったので?
はunwrap
のように値を取り出しています。だからといってどこでも?演算子
が使えるわけでもありません。?演算子
が使えるのは、それを使う関数の戻り値がResult
であることです。この例ではmain
の戻り値がResult
になっているから使えます。さらにErr(E)
の型Eが同じでなければなりません。この例ではString
で同じになっています。
次はsub_func
でErr
にする。
fn main() -> Result<(), String> {
println!("enter main");
let rs: u32 = sub_func()?;
println!("sub_func returned: {}", rs);
Ok(())
}
fn sub_func() -> Result<u32, String> {
println!("enter sub_func");
Err("sub_func error".into())
}
enter main
enter sub_func
Error: "sub_func error"
Err
が返ってくると?演算子
は関数内の以降の処理を飛ばしてreturnさせる。さらにpanic
にはなっていない。
match
を使って書き直すと次のようになります。
let rs = match sub_func(){
Ok(val) => val,
Err(err) => return Err(err),
};
match
を使った例でもわかるようにErr(E)
の型Eが一致していないと使えないのは前述したと通りです。例では、型Eがmain
,sub_func
共にString
なので?演算子
が使えた。
その他のメソッド
if let
let o1: Option<i32> = Some(5);
if let Some(val) = o1 {
println!("o1 value: {}", val);
} else {
println!("o1 value is missing");
}
let r1: Result<i32, String> = Err("Error occurred".into());
if let Ok(val) = r1 {
println!("r1 value: {}", val);
} else {
println!("r1 value is missing: \"{}\"", r1.unwrap_err());
}
o1 value: 5
r1 value is missing: "Error occurred"
unwrap_err()
でErr
の内容を取得できます。
is_some(), is_ok()
let o1: Option<i32> = Some(5);
if o1.is_some(){
println!("o1 value: {}", o1.unwrap());
} else {
println!("o1 value is missing");
}
let r1: Result<i32, String> = Err("Error occurred".into());
if r1.is_ok() {
println!("r1 value: {}", r1.unwrap());
} else {
println!("r1 value is missing: \"{}\"", r1.unwrap_err());
}
o1 value: 5
r1 value is missing: "Error occurred"
これらとは逆のis_none()
, is_err()
もあります。
unwrap_or
None
やErr
のときに正常値とは別の値を設定することができます。
let o1: Option<i32> = None;
let o1_val = o1.unwrap_or(0);
println!("o1 value: {}", o1_val);
let r1: Result<i32, String> = Err("Error occurred".into());
let r1_val = r1.unwrap_or(-1);
println!("r1 value: {}", r1_val);
o1 value: 0
r1 value: -1
当然Some(5)
やOk(12)
のときはそれぞれ5
や12
になります。
この他にはunwrap_or_else
などもあります。
終わりに
Option
やResult
で使えるメソッドはたくさんあって使い方や名前を覚えるのが大変です。その他のメソッドについてはマニュアルを参照すると良いと思います。
記事を書くことで理解が深まった気がします。