エラー処理に関するメソッド
エラーをスムーズに処理するには、公式が提供するメソッドを適切に活用することが重要です。うまく活用すれば効率が大幅に向上します。
or()
and()
or_else()
and_then()
map()
map_err()
map_or()
map_or_else()
ok_or()
ok_or_else()
- …
以下では、これらのメソッドをどのような場面で使うべきか、どのように使うのかを詳しく説明し、最終的に Err
型を設計する際のポイントについても紹介します。
or()
, and()
or()
と and()
は、どちらか一方を選択する際に使用します。論理演算の OR(または)と AND(かつ)に似ています。
-
or()
は、式を順番に評価し、いずれかの式の結果がSome
またはOk
であれば、その値を即座に返します。 -
and()
は、両方の式の結果がSome
またはOk
の場合に、2 つ目の式の値を返します。どちらか一方がNone
またはErr
の場合は、その値を即座に返します。
let s1 = Some("some1");
let s2 = Some("some2");
let n: Option<&str> = None;
let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");
assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1
assert_eq!(s1.or(n), s1); // Some or None = Some
assert_eq!(n.or(s1), s1); // None or Some = Some
assert_eq!(n.or(n), n); // None1 or None2 = None2
assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1
assert_eq!(o1.or(e1), o1); // Ok or Err = Ok
assert_eq!(e1.or(o1), o1); // Err or Ok = Ok
assert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2
assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2
assert_eq!(s1.and(n), n); // Some and None = None
assert_eq!(n.and(s1), n); // None and Some = None
assert_eq!(n.and(n), n); // None1 and None2 = None1
assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2
assert_eq!(o1.and(e1), e1); // Ok and Err = Err
assert_eq!(e1.and(o1), e1); // Err and Ok = Err
assert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1
or_else()
, and_then()
or()
と and()
は単純な選択をするだけで、値を変更することはできません。もしエラー時に異なる処理を実行したい場合は、クロージャ(無名関数)を使う必要があります。そのために or_else()
と and_then()
を使用します。
// `or_else()` の Option 版
let s1 = Some("some1");
let s2 = Some("some2");
let fn_some = || Some("some2");
let n: Option<&str> = None;
let fn_none = || None;
assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 = Some1
assert_eq!(s1.or_else(fn_none), s1); // Some or_else None = Some
assert_eq!(n.or_else(fn_some), s2); // None or_else Some = Some
assert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2
// `or_else()` の Result 版
let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let fn_ok = |_| Ok("ok2");
let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");
let fn_err = |_| Err("error2");
assert_eq!(o1.or_else(fn_ok), o1); // Ok1 or_else Ok2 = Ok1
assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Ok
assert_eq!(e1.or_else(fn_ok), o2); // Err or_else Ok = Ok
assert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2
map()
Result
や Option
の値を変換する場合は map()
を使用します。
let s1 = Some("abcde");
let s2 = Some(5);
let n1: Option<&str> = None;
let n2: Option<usize> = None;
let o1: Result<&str, &str> = Ok("abcde");
let o2: Result<usize, &str> = Ok(5);
let e1: Result<&str, &str> = Err("abcde");
let e2: Result<usize, &str> = Err("abcde");
let fn_character_count = |s: &str| s.chars().count();
assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2
assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2
assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2
map_err()
Result
の Err
の値を変更する場合は map_err()
を使用します。
let o1: Result<&str, &str> = Ok("abcde");
let o2: Result<&str, isize> = Ok("abcde");
let e1: Result<&str, &str> = Err("404");
let e2: Result<&str, isize> = Err(404);
let fn_character_count = |s: &str| -> isize { s.parse().unwrap() };
assert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2
map_or()
エラーが発生しない場合、デフォルト値を返す map_or()
を使用できます。
const V_DEFAULT: u32 = 1;
let s: Result<u32, ()> = Ok(10);
let n: Option<u32> = None;
let fn_closure = |v: u32| v + 2;
assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);
assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
map_or_else()
map_or()
はデフォルト値を固定で指定しますが、map_or_else()
を使えば、クロージャを使ってデフォルト値を動的に生成できます。
let s = Some(10);
let n: Option<i8> = None;
let fn_closure = |v: i8| v + 2;
let fn_default = || 1;
assert_eq!(s.map_or_else(fn_default, fn_closure), 12);
assert_eq!(n.map_or_else(fn_default, fn_closure), 1);
let o = Ok(10);
let e = Err(5);
let fn_default_for_result = |v: i8| v + 1;
assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);
assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
ok_or()
Option
を Result
に変換する場合は ok_or()
を使用します。
const ERR_DEFAULT: &str = "error message";
let s = Some("abcde");
let n: Option<&str> = None;
let o: Result<&str, &str> = Ok("abcde");
let e: Result<&str, &str> = Err(ERR_DEFAULT);
assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T)
assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -> Err(default)
ok_or_else()
ok_or()
は固定のエラーメッセージを返しますが、ok_or_else()
を使えばクロージャでエラーメッセージを動的に作成できます。
let s = Some("abcde");
let n: Option<&str> = None;
let fn_err_message = || "error message";
let o: Result<&str, &str> = Ok("abcde");
let e: Result<&str, &str> = Err("error message");
assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -> Ok(T)
assert_eq!(n.ok_or_else(fn_err_message), e); // None -> Err(default)
Error
の設計
初心者は Result
の型の違いに苦しむことが多いですが、Result
の型を明確に理解すれば、エラー処理で困ることはなくなります。
シンプルなカスタムエラー
通常、プログラムでは独自のエラー型を定義します。まずは最もシンプルな Result
のカスタムエラーを作成しましょう。
use std::fmt;
#[derive(Debug)]
struct CustomError;
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "An Error Occurred, Please Try Again!")
}
}
fn make_error() -> Result<(), CustomError> {
Err(CustomError)
}
fn main() {
match make_error() {
Err(e) => eprintln!("{}", e),
_ => println!("No error"),
}
eprintln!("{:?}", make_error());
}
eprintln!
は println!
と基本的に同じですが、標準エラー出力に出力されます。
カスタムエラーに Debug
と Display
を実装することで、エラーを適切に表示できます。また、Box<dyn std::error::Error>
に変換するためにも必要になります。
より複雑なカスタムエラー
現実のプロジェクトでは、エラーコードやエラーメッセージを含めることが多いです。
use std::fmt;
struct CustomError {
code: usize,
message: String,
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let err_msg = match self.code {
404 => "Sorry, Can not find the Page!",
_ => "Sorry, something is wrong! Please Try Again!",
};
write!(f, "{}", err_msg)
}
}
impl fmt::Debug for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"CustomError {{ code: {}, message: {} }}",
self.code, self.message
)
}
}
fn make_error() -> Result<(), CustomError> {
Err(CustomError {
code: 404,
message: String::from("Page not found"),
})
}
fn main() {
match make_error() {
Err(e) => eprintln!("{}", e), // "Sorry, Can not find the Page!"
_ => println!("No error"),
}
eprintln!("{:?}", make_error()); // "CustomError { code: 404, message: Page not found }"
}
このように Display
と Debug
を手動実装することで、エラーの表現を自由にカスタマイズできます。
エラーの変換
通常、プログラムではさまざまなライブラリの Result
を扱いますが、それらの Err
型は統一されていません。異なるエラー型を統一するために std::convert::From
を利用します。
use std::fs::File;
use std::io;
#[derive(Debug)]
struct CustomError {
kind: String,
message: String,
}
impl From<io::Error> for CustomError {
fn from(error: io::Error) -> Self {
CustomError {
kind: String::from("io"),
message: error.to_string(),
}
}
}
fn main() -> Result<(), CustomError> {
let _file = File::open("nonexistent_file.txt")?;
Ok(())
}
このコードでは、?
を使うことで std::io::Error
を CustomError
に自動変換しています。
より多くのエラー型を統一するには、さらに From
を実装すれば OK です。
use std::fs::File;
use std::io::{self, Read};
use std::num;
#[derive(Debug)]
struct CustomError {
kind: String,
message: String,
}
impl From<io::Error> for CustomError {
fn from(error: io::Error) -> Self {
CustomError {
kind: String::from("io"),
message: error.to_string(),
}
}
}
impl From<num::ParseIntError> for CustomError {
fn from(error: num::ParseIntError) -> Self {
CustomError {
kind: String::from("parse"),
message: error.to_string(),
}
}
}
fn main() -> Result<(), CustomError> {
let mut file = File::open("hello_world.txt")?;
let mut content = String::new();
file.read_to_string(&mut content)?;
let _number: usize;
_number = content.parse()?;
Ok(())
}
Result
の異なる Err
型を統一する方法
プログラム内で異なる Err
型が混在する場合、以下の 4 つの方法で統一できます。
-
Box<dyn Error>
を使用 - 独自のエラー型を定義
-
thiserror
を使用 -
anyhow
を使用
1. Box<dyn Error>
を使用
std::error::Error
トレイトを実装していれば、Box<dyn Error>
で統一できます。
use std::fs::read_to_string;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String, Box<dyn Error>> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
この方法は簡単ですが、以下のデメリットがあります:
- 動的ディスパッチ:型情報が失われ、パフォーマンスがわずかに低下する。
- ヒープ割り当て:エラーをヒープに格納するため、オーバーヘッドが発生する。
- 型安全性の低下:具体的なエラー型を扱いにくくなる。
2. 独自のエラー型を定義
エラーを列挙型(enum
)で管理すれば、型安全性が向上します。
use std::fs::read_to_string;
fn main() -> Result<(), MyError> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String, MyError> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
#[derive(Debug)]
enum MyError {
EnvironmentVariableNotFound,
IOError(std::io::Error),
}
impl From<std::env::VarError> for MyError {
fn from(_: std::env::VarError) -> Self {
Self::EnvironmentVariableNotFound
}
}
impl From<std::io::Error> for MyError {
fn from(value: std::io::Error) -> Self {
Self::IOError(value)
}
}
impl std::error::Error for MyError {}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MyError::EnvironmentVariableNotFound => write!(f, "Environment variable not found"),
MyError::IOError(err) => write!(f, "IO Error: {}", err.to_string()),
}
}
}
この方法は型安全ですが、やや冗長です。
3. thiserror
を使用
thiserror
を使うと、エラーの定義が簡潔になります。
use std::fs::read_to_string;
use thiserror::Error;
fn main() -> Result<(), MyError> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String, MyError> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
#[derive(Error, Debug)]
enum MyError {
#[error("Environment variable not found")]
EnvironmentVariableNotFound(#[from] std::env::VarError),
#[error(transparent)]
IOError(#[from] std::io::Error),
}
マクロを使って From
実装を自動化できるので、非常に便利です。
4. anyhow
を使用
anyhow
は Box<dyn Error>
のラッパーですが、エラーハンドリングをより簡単にできます。
use std::fs::read_to_string;
use anyhow::Result;
fn main() -> Result<()> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
Just do it
これで Rust のエラー処理が怖くなくなったはずです。上記の方法を活用すれば、実際の開発でエラー処理に困ることはなくなります!
私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ