0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

(DAY20) 俺と学ぶRust言語~エラーハンドリング~

Posted at

今日の内容

  • 「Option」と「Result」を組み合わせたエラーハンドリングの応用

はじめに

前回は、Resultについて学びました。
今回はOptionとResultを組み合わせたエラーハンドリングの応用について学んでいきます。

OptionとResultの相互変換

Rustでは、OptionをResultに変換したり、その逆を行うことができます。

OptionからResultに変換

「値がない(None)」場合にエラーを設定したいときに使います。

fn get_user_id(input: Option<&str>) -> Result<i32, String> {
    input
        .ok_or_else(|| "値がありません".to_string())
        .and_then(|id_str| id_str.parse::<i32>().map_err(|_| "Please type a number!".to_string()))
}
fn main() {
    let user_id = get_user_id(Some("42"));
    println!("{:?}", user_id);

    let invalid_user_id = get_user_id(Some("abc"));
    println!("{:?}", invalid_user_id);

    let none_user_id = get_user_id(None);
    println!("{:?}", none_user_id);
}
/******** 実行結果 ********
Ok(42)
Err("Please type a number!")
Err("値がありません")
*************************/

ResultからOptionに変換

エラー情報が不要な場合は、ok()を使ってResultをOptionに変換します。

fn main() {
    let result: Result<i32, &str> = Ok(10);
    let option = result.ok();
    println!("{:?}", option);

    let error_result: Result<i32, &str> = Err("err");
    let option = error_result.ok();
    println!("{:?}", option);
}
/******** 実行結果 ********
Some(10)
None
*************************/

複数の処理を組み合わせる

以下の例では、ファイルの内容を読み取り、数値を計算する処理を安全に行っています。

use std::fs::File;
use std::io::{self, Read};

fn read_file_content(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

fn calculate_average(data: &str) -> Result<f64, &'static str> {
    let numbers: Result<Vec<f64>, _> = data
        .lines()
        .map(|line| line.trim().parse::<f64>())
        .collect();
    let numbers = numbers.map_err(|_| "parse failed!")?;
    if numbers.is_empty() {
        Err("Data is empty!")
    } else {
        Ok(numbers.iter().sum::<f64>() / numbers.len() as f64)
    }
}

fn main() {
    match read_file_content("data.txt") {
        Ok(content) => match calculate_average(&content) {
            Ok(avg) => println!("average: {:.2}", avg),
            Err(e) => println!("Err: {}", e),
        },
        Err(e) => println!("File error: {}", e),
    }
}
  • ファイル読み取りのエラーはResultで管理
  • 文字列から数値を解析する際もResultでエラーを処理
  • matchでネストして、全てのエラーケースに対応

?演算子を使った簡略化

ネストを避けるために、?演算子を活用してコードを簡潔に書き直します。

fn process_file(path: &str) -> Result<f64, Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string(path)?;
    let numbers: Vec<f64> = content
        .lines()
        .map(|line| line.trim().parse::<f64>())
        .collect::<Result<_, _>>()?;
    if numbers.is_empty() {
        return Err("Data is empty".into());
    }
    Ok(numbers.iter().sum::<f64>() / numbers.len() as f64)
}
fn main() {
    match process_file("data.txt") {
        Ok(avg) => println!("avarage: {:.2}", avg),
        Err(e) => println!("Err: {}", e),
    }
}
  • ?を使ってエラーが発生した場合に、即座に関数を終了できる
  • ネストを減らし、コードの可読性を向上

OptionとResultの併用例

以下のコードでは、入力を処理し、条件に応じてエラーまたは成功を返します。

fn validate_input(input: Option<&str>) -> Result<i32, &'static str> {
    let input = input.ok_or("No input")?;
    let number: i32 = input.parse().map_err(|_| "please type number!")?;
    if number < 0 {
        Err("Negative values not allowed")
    } else {
        Ok(number)
    }
}
fn main() {
    let inputs = vec![Some("42"), Some("-10"), None, Some("abc")];

    for input in inputs {
        match validate_input(input) {
            Ok(value) => println!("input value: {}", value),
            Err(e) => println!("Err: {}", e),
        }
    }
}
/******** 実行結果 ********
input value: 42
Err: Negative values not allowed
Err: No input
Err: please type number!
*************************/

複数のエラー型の統合

複数の関数が異なるエラー型を返す場合、統一的に扱うためにBox<dyn std::error::Error>thiserrorクレートを利用します。

use std::error::Error;

fn read_file(path: &str) -> Result<String, Box<dyn Error>> {
    let content = std::fs::read_to_string(path)?;
    Ok(content)
}

fn parse_content(content: &str) -> Result<Vec<i32>, Box<dyn Error>> {
    let numbers = content
        .lines()
        .map(|line| line.parse::<i32>())
        .collect::<Result<Vec<_>, _>>()?;
    Ok(numbers)
}

fn main() {
    let result = (|| -> Result<(), Box<dyn Error>> {
        let content = read_file("data.txt")?;
        let numbers = parse_content(&content)?;
        println!("Data: {:?}", numbers);
        Ok(())
    })();

    if let Err(e) = result {
        println!("Err: {}", e);
    }
}

おわりに

今回は、OptionとResultを組み合わせたエラーハンドリングの応用について学びました。
次回は、if let について学びたいと思います。
ご精読ありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?