`Result`による堅牢なエラー処理
`Result`列挙型、`?`演算子、カスタムエラー型を使い、回復力のあるアプリケーションのためのRustらしいエラー処理を実装します。
「`Result`による堅牢なエラー処理」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLearn Rust Coding学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Learn Rust Codingコースには全3レッスンが含まれています。
Rust でのエラー処理
エラーはプログラミングにつきものです。Rust では、エラーを主に回復可能なエラーと回復不能なエラーに分けます。
- 回復不能なエラーは通常、バグを示すもので、
panic!によってプログラムが停止します。 - 回復可能なエラーは、ファイルが見つからない場合のように、問題は発生したものの対処できるエラーです。
Rust では、回復可能なエラーを適切に処理できます。
`panic!` と `Result` の比較
回復不能なエラーが発生すると、Rust は panic! マクロを呼び出します。これによりスタックが巻き戻されてプログラムが終了し、通常はエラーメッセージが表示されます。
回復可能なエラーには、Rust の Result 列挙型を使います。この列挙型は、処理が成功したか(Ok)、失敗したか(Err)を示すため、プログラム側で次の処理を判断できます。
`Result` 列挙型
Result 列挙型は、概念的には次のように定義されます。
enum Result<T, E> {
Ok(T),
Err(E),
}
Tは、成功時に返される値の型を表します。Eは、失敗時に返されるエラーの型を表します。
これは、関数が失敗する可能性を明示的に示す強力な方法です。
`Result` のパターンマッチ
Result を処理する最も一般的な方法は、match 式を使うことです。これにより、Result が Ok か Err かに応じて、異なるコードブロックを実行できます。
数値の解析を試みる次の例を実行してみましょう。
fn parse_and_add(text: &str) -> Result<i32, std::num::ParseIntError> {
text.parse::<i32>()
}
fn main() {
let num_str = "123";
let result = parse_and_add(num_str);
match result {
Ok(num) => println!("Parsed number: {}", num),
Err(e) => println!("Error parsing: {}", e),
}
let bad_str = "abc";
let bad_result = parse_and_add(bad_str);
match bad_result {
Ok(num) => println!("Parsed number: {}", num),
Err(e) => println!("Error parsing: {}", e),
}
}Result のアンラップ(慎重に)
処理が失敗しないと確信できる場合や、失敗したらプログラムをクラッシュさせたい場合があります。unwrap() や expect() などのメソッドは、Ok の値を取り出します。
unwrap():Okの値を返します。Errの場合はパニックを起こします。expect("message"):unwrap()と同じですが、カスタムパニックメッセージを指定できます。
これらは、主にテストや、失敗から本当に回復できない場合に限り、慎重に使用してください。
`?` 演算子の導入
すべての Result に対して match 文を書くと、コードが冗長になることがあります。? 演算子を使うと、エラーの伝播を簡潔に記述できます。
Result を返す式の後に ? を置くと、次の2つの処理が行われます。
ResultがErrの場合、現在の関数からそのErrを直ちに返します。ResultがOkの場合、Okの値をアンラップして実行を続けます。
`?` 演算子の使用例
? 演算子を使うと、失敗する可能性のある関数をすっきり記述できます。ただし、? を使う関数自体が Result(または Option)を返す必要があることを覚えておいてください。
次のファイル読み込みの例で、? によってエラー処理がどのように簡潔になるか確認しましょう。
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?; // Propagates error if file not found
let mut s = String::new();
f.read_to_string(&mut s)?; // Propagates error if read fails
Ok(s)
}
fn main() {
// Create a dummy file for the example to work
// In a real scenario, this file might not exist
std::fs::write("hello.txt", "CoddyKit User").unwrap();
match read_username_from_file() {
Ok(username) => println!("Username: {}", username),
Err(e) => println!("Error reading username: {}", e),
}
// Clean up the dummy file
std::fs::remove_file("hello.txt").unwrap();
}カスタムエラー型
組み込みのエラー型では、十分に具体的でない場合があります。列挙型を使って独自のカスタムエラー型を定義し、アプリケーションで起こり得るさまざまな失敗を表現できます。
これにより、エラーメッセージがわかりやすくなり、呼び出し側で特定のエラーごとに異なる処理を行えるようになります。
よく使われる方法は、関数やモジュールが生成する可能性のあるすべてのエラーを列挙する enum を作成することです。
カスタムエラーで `?` を使う
? 演算子によって低レベルのエラー(io::Error など)をカスタムエラー型に自動変換するには、カスタムエラーの enum に、その低レベルのエラーに対する From トレイトを実装する必要があります。
これにより、Rust にあるエラー型から別のエラー型へ変換する方法を伝えられるため、エラーをスムーズに伝播できます。
use std::io;
use std::fs::File;
use std::io::Read;
#[derive(Debug)]
enum MyError {
Io(io::Error),
Parse(std::num::ParseIntError)
}
impl From<io::Error> for MyError {
fn from(error: io::Error) -> Self {
MyError::Io(error)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(error: std::num::ParseIntError) -> Self {
MyError::Parse(error)
}
}
fn read_config(path: &str) -> Result<i32, MyError> {
let mut file = File::open(path)?; // io::Error converted to MyError::Io
let mut contents = String::new();
file.read_to_string(&mut contents)?; // io::Error converted to MyError::Io
let value = contents.trim().parse::<i32>()?; // ParseIntError converted to MyError::Parse
Ok(value)
}
fn main() {
// Create a dummy file for the example
std::fs::write("config.txt", "123").unwrap();
match read_config("config.txt") {
Ok(val) => println!("Config value: {}", val),
Err(e) => println!("Failed to read config: {:?}", e),
}
std::fs::remove_file("config.txt").unwrap(); // Clean up
// Example with a non-existent file
match read_config("non_existent.txt") {
Ok(val) => println!("Config value: {}", val),
Err(e) => println!("Failed to read config: {:?}", e),
}
}理解度チェック
次の Rust コードについて考えてみましょう。ファイル data.txt が存在しない場合、出力はどうなりますか。
復習: 堅牢なエラー処理
Rust で堅牢なエラー処理を使いこなせるようになりました。
Result列挙型: 成功を表すOk(T)と、失敗を表すErr(E)を区別します。match:Resultの各バリアントを処理する基本的な方法です。?演算子:Errの値を呼び出し元へ伝播するための強力な省略記法です。- カスタムエラー: 列挙型を使って独自のエラー型を定義し、明確で具体的な処理を実現します。
Fromトレイト:?を使う際に、低レベルのエラーをカスタムエラー型へスムーズに変換できるようにします。
次は、型システムの高度な機能を見ていきましょう。
よくある質問
「`Result`による堅牢なエラー処理」レッスンは無料ですか?
はい。「`Result`による堅牢なエラー処理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全3レッスンが含まれています。
「`Result`による堅牢なエラー処理」で何を学びますか?
`Result`列挙型、`?`演算子、カスタムエラー型を使い、回復力のあるアプリケーションのためのRustらしいエラー処理を実装します。 ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Learn Rust Codingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのLearn Rust Codingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。
「`Result`による堅牢なエラー処理」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このLearn Rust Codingレッスンでコードを書いて実行できますか?
はい。すべてのLearn Rust Codingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- モジュールによるコード整理
- Crateによる依存関係管理
- `Result`による堅牢なエラー処理