rust: Result<T, E> と Option<T>
.unwrap()
doc.rust-lang.org で検索すると、単語一致で “unwrap” を持っているのは Result::unwrap と Option::unwrap だけだった。
Result<T, E>
Result<T, E>.unwrap() は T か E しか返さないのでわかりやすい。
T は Ok(T) に、E は Err(E) になる。
Ok() や Err() に何か実装があるわけではなく、そういう意味づけがある enum値というだけのようだ。
.unwrap() には where があるので E が何でもよいわけでは無くデバッグ出力っぽいことができないとダメそうだ。
そういうときでも .unwrap_なんちゃら() を探せばほどよいのがあるだろう。
Return<> を使いたいだけであればそういう制約はない。
pub enum Result<T, E> {
/// Contains the success value
#[lang = "Ok"]
#[stable(feature = "rust1", since = "1.0.0")]
Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
/// Contains the error value
#[lang = "Err"]
#[stable(feature = "rust1", since = "1.0.0")]
Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}
......
pub fn unwrap(self) -> T
where
E: fmt::Debug,
{
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
}
}
......
Option<T>
[Option
Rust には言語として null 的な定義は無い。その代わりになる enum Option を標準ライブラリに用意した。
言語としてではなくライブラリとして最初からあるというだけなので、自分で同じようなことをやってもよいということだ。
Rust として null を組み入れたくはないけど便利だよねということらしい。
.unwrap() は None 側で panic するので Result のような制約はない。
その分、なんで panic したのかはわかりづらいかもしれん。
pub enum Option<T> {
/// No value.
#[lang = "None"]
#[stable(feature = "rust1", since = "1.0.0")]
None,
/// Some value of type `T`.
#[lang = "Some"]
#[stable(feature = "rust1", since = "1.0.0")]
Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}
......
pub const fn unwrap(self) -> T {
match self {
Some(val) => val,
None => unwrap_failed(),
}
}
......
? が使えるところ
演算子の表の最後に ? があり「エラー委譲」とある。英文では “Error propagation”。
propagation は委譲というよりは伝播っていう感じがする。
? は、Ok(T) なら T が戻されて続く。Err(E) なら E が戻されるが return E になる。
正常時は .unwrap() と同じで、エラーの場合は呼び元にエラーが戻される。
標準ライブラリにあるからといって ? が Err() 専用になっているわけではなくオーバーロードするしくみがある。
“Experimental” となっているが ? になりそうなのがこれしかない。
branch() は ? が使われたときに値を返す(Continue)か呼び出し元に伝播させるか(Break)を判定する処理、from_output() が ? で正常系だったときに返す値だそうだ。
Result にあった Try はこれ。
impl<T, E> const ops::Try for Result<T, E> {
type Output = T;
type Residual = Result<convert::Infallible, E>;
#[inline]
fn from_output(output: Self::Output) -> Self {
Ok(output)
}
#[inline]
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Ok(v) => ControlFlow::Continue(v),
Err(e) => ControlFlow::Break(Err(e)),
}
}
}
Option にあった Try はこれ。
impl<T> const ops::Try for Option<T> {
type Output = T;
type Residual = Option<convert::Infallible>;
#[inline]
fn from_output(output: Self::Output) -> Self {
Some(output)
}
#[inline]
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Some(v) => ControlFlow::Continue(v),
None => ControlFlow::Break(None),
}
}
}
anyhow
Result<T, E> とすると E が固定の型になっていろいろなエラーを受け付ける可能性がある関数では使いづらい。
anyhow::Result を使って Result<T, anyhow::Error> とすることで「いろいろ」を受け付けられるようになる。
戻り値が Result<T> だけになっている場合はほぼ anyhow::Result<T> のことでこの形式になっていると思って良いだろう。
「いろいろ」といっても anyhow::Error に合致する必要はある。
単にエラー文字列を返したいときは return Err(anyhow!(msg)) や anyhow::bail!(msg)
anyhow で便利なのは ? でエラーを返すときにメッセージを付与する .context(msg)? という書き方ができるところだ。
Option を anyhow::Result にしたい
全体的に Result<T, E> でエラーを伝播させていたのだが、Option<T> を返すメソッドもある。
そういう場合はどちらの戻り値(ここでは Result)に一本化したいだろう。
今やっている例を挙げると、下側からどういう E が来るのかわからないので anyhow::Result を使っている。
Result<()> を返すメソッドの中で Option<u32> を返すメソッドがあるので何とかしたい。
? を使ったらうまいことやってくれないかと期待したが、さすがにダメそうだった。
Gemini か Copilot にやってもらうと match のアームで None => { return Err(anyhow::anyhow!("エラーが起きた")); }, のようにしていた。
文字列型のエラーを返したいだけにしては大げさ気がするが、anyhow だからこうなるんだろうか。
ネットで検索すると Option を Result にする例として .ok_or(値) や .ok_or_else(関数) などを使うそうだ。
こんな感じで変換できそうだ。
.ok_or() で .to_owned() を使っているのは extension で修正候補が出たから使っただけだ。
to_owned() は借用データから所有データを作り出すものだそうだ。
なら .clone() でいいんじゃないのと思ったがダメだったが .to_string() は通る。
use std::process;
fn ldl_hdl(msg: &str) -> Option<String> {
Some(format!("Your {} is too high.", msg))
// None
}
fn cholesterol(msg: &str) -> Result<String, String> {
ldl_hdl(msg).ok_or("None!!!".to_owned())
}
fn main() {
let ret = match cholesterol("LDL") {
Ok(s) => s,
Err(e) => {
eprintln!("cholesterol() error: {}", e.to_string());
process::exit(1)
},
};
println!("{}", ret);
}