エラーハンドリング入門
プログラムで起こりうるエラーを適切に処理する方法を学びます。
Rustのエラー処理の考え方
多くの言語では「例外」を使いますが、Rustは型システムでエラーを扱います。
他の言語: 成功 or 例外(突然プログラムが止まる)
Rust: Result<成功, 失敗> を返す(呼び出し側が処理を決める)
これにより、エラーを「忘れて無視する」ことが難しくなります。
Option型:値があるかないか
Optionは「値があるかもしれないし、ないかもしれない」を表します。
Option の定義(イメージ)
#![allow(unused)]
fn main() {
enum Option<T> {
Some(T), // 値がある
None, // 値がない
}
}
使用例
fn main() {
let numbers = vec![1, 2, 3];
// インデックス1の要素を取得
match numbers.get(1) {
Some(n) => println!("見つかった: {}", n),
None => println!("見つからない"),
}
// インデックス10の要素を取得(範囲外)
match numbers.get(10) {
Some(n) => println!("見つかった: {}", n),
None => println!("見つからない"),
}
}
出力:
見つかった: 2
見つからない
なぜ Option を使うのか
#![allow(unused)]
fn main() {
// 他の言語でよくあるパターン(危険)
// let value = array[10]; // 範囲外 → クラッシュ!
// Rustのパターン(安全)
let value = numbers.get(10); // Option<&i32> を返す
// 必ず Some/None を処理する必要がある
}
Option の便利なメソッド
fn main() {
let some_number = Some(5);
let no_number: Option<i32> = None;
// unwrap_or: Noneの場合のデフォルト値
println!("{}", some_number.unwrap_or(0)); // 5
println!("{}", no_number.unwrap_or(0)); // 0
// is_some / is_none
println!("{}", some_number.is_some()); // true
println!("{}", no_number.is_none()); // true
}
Result型:成功か失敗か
Resultは「処理が成功したか失敗したか」を表します。
Result の定義(イメージ)
#![allow(unused)]
fn main() {
enum Result<T, E> {
Ok(T), // 成功(Tは成功時の値)
Err(E), // 失敗(Eはエラー情報)
}
}
使用例
fn main() {
let result = divide(10, 2);
match result {
Ok(value) => println!("結果: {}", value),
Err(e) => println!("エラー: {}", e),
}
let result = divide(10, 0);
match result {
Ok(value) => println!("結果: {}", value),
Err(e) => println!("エラー: {}", e),
}
}
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("ゼロで割ることはできません"))
} else {
Ok(a / b)
}
}
出力:
結果: 5
エラー: ゼロで割ることはできません
ファイル操作の例
use std::fs;
fn main() {
let result = fs::read_to_string("hello.txt");
match result {
Ok(content) => println!("内容: {}", content),
Err(e) => println!("読み込みエラー: {}", e),
}
}
ファイルが存在しない場合、Errが返されます。
unwrapとexpect(注意が必要)
unwrap
SomeまたはOkの値を取り出します。NoneやErrの場合はパニック(プログラム停止)。
fn main() {
let some_value = Some(5);
let value = some_value.unwrap(); // 5
println!("{}", value);
// let none_value: Option<i32> = None;
// let value = none_value.unwrap(); // パニック!
}
expect
unwrapと同じですが、エラーメッセージを指定できます。
fn main() {
let result: Result<i32, &str> = Err("何か問題が発生");
// let value = result.expect("致命的なエラー"); // パニック!メッセージ付き
}
unwrap/expectを使っていい場面
- 学習中・プロトタイプ: 素早く動かしたいとき
- 絶対に失敗しない場合: 論理的に失敗しないことが保証されるとき
#![allow(unused)]
fn main() {
// OK: "42"は必ず数値に変換できる
let number: i32 = "42".parse().unwrap();
}
本番コードでは避ける
#![allow(unused)]
fn main() {
// 避ける
let content = fs::read_to_string("config.txt").unwrap();
// 推奨
let content = fs::read_to_string("config.txt")
.expect("設定ファイルの読み込みに失敗"); // せめてメッセージを
// より良い: エラーを伝播
fn read_config() -> Result<String, std::io::Error> {
fs::read_to_string("config.txt")
}
}
if letによる簡潔な処理
特定のパターンだけ処理したい場合に便利です。
fn main() {
let some_value = Some(5);
// matchを使う場合
match some_value {
Some(n) => println!("値: {}", n),
None => {} // 何もしない
}
// if letを使う場合(簡潔)
if let Some(n) = some_value {
println!("値: {}", n);
}
}
まとめ
| 型 | 用途 | バリアント |
|---|---|---|
Option<T> | 値の有無 | Some(T), None |
Result<T, E> | 成功/失敗 | Ok(T), Err(E) |
| メソッド | 動作 | 注意 |
|---|---|---|
unwrap() | 値を取り出す | 失敗でパニック |
expect("msg") | 値を取り出す | 失敗でメッセージ付きパニック |
unwrap_or(default) | 値またはデフォルト | 安全 |
is_some() / is_ok() | 成功かチェック | 安全 |
確認テスト
Q1. Option<T>のNoneは何を表す?
Noneは「値がない」ことを表します。エラーではなく、単に値が存在しない状態です。エラーを表すにはResult<T, E>のErrを使います。
Q2. unwrap()を使う際のリスクは?
unwrap()はSomeやOkの値を取り出しますが、NoneやErrの場合はプログラムがパニックして停止します。
Q3. 以下のコードの出力は?let numbers = vec![10, 20, 30]; let value = numbers.get(5).unwrap_or(&0); println!("{}", value);
numbers.get(5)はインデックス5が範囲外なのでNoneを返します。unwrap_or(&0)はNoneの場合にデフォルト値&0を返すので、結果は0です。
Q4. unwrap()を使わずに安全にOptionを処理する方法として適切でないのは?
Option型はasでキャストできません。match、if let、unwrap_orなどを使って安全に値を取り出します。
Q5. safe_divide(10, 0)がNoneを返す関数を実装する場合、正しいのは?
Option<i32>を返す場合、成功時はSome(値)、失敗時はNoneを返します。Resultを使う場合はC)のような形になります。
Phase 1 完了!
おめでとうございます!Phase 1を完了しました。
学んだこと:
- 変数と型(let、mut、基本型)
- 演算子(算術、比較、論理)
- 制御フロー(if、loop、while、for、match)
- 関数(引数、戻り値、式と文)
- コレクション(配列、タプル、Vec)
- エラーハンドリング入門(Option、Result)
次のPhase: Phase 2: 所有権とメモリ
Phase 2はRust最重要の概念「所有権」を学びます。ここが理解できればRustマスターへの道が開けます!