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?

【Effective-Rustlings-jp】Day 4:標準のErrorを使おう

Last updated at Posted at 2025-01-04

はじめに

こんにちは。細々とプログラミングをしているsotanengelです。
この記事は以下の記事の連載です。

他の連載記事 (詳細)

また本記事はEffective Rust(David Drysdale (著), 中田 秀基 (翻訳))を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください!

今日の内容

概要

RustのErrorトレイトを独自のエラー型に実装することで、呼び出し側に適切にエラー情報を伝えられるようにしよう。

String情報を持つ独自のエラーを実装しよう

問(リンク)

StringErrorとしてそのまま使うことはできないため、独自の型(OriginalError)を定義しましょう。

コード (詳細)
// TODO: StringはErrorを実装していないため、formatが使えるように独自の型を定義し、Errorを実装してください。
fn do_something(flag: bool) -> Result<(), String> {
    if flag {
        Ok(())
    } else {
        "Something went wrong".to_string()
    }
}

fn main() {
    match do_something(false) {
        Ok(_) => println!("Success!"),
        Err(e) => {
            let error_message = format!("Failed: {}", e);
            println!("{}", error_message);
        }
    }
}

解答(リンク)

Fromトレイトも実装することで、Stringからの変換も容易にすることができます。

コード (詳細)
use std::error;
use std::fmt;

// 独自エラー型の定義 (タプル構造体)
#[derive(Debug)]
pub struct OriginalError(String);

// Displayトレイトを実装してエラーメッセージを整える
impl fmt::Display for OriginalError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error: {}", self.0)
    }
}

// error::Errorトレイトを実装
impl error::Error for OriginalError {}

// From<String> を実装して String からの変換を簡単に
impl From<String> for OriginalError {
    fn from(message: String) -> Self {
        OriginalError(message)
    }
}

// &str からの変換もできるようにする
impl From<&str> for OriginalError {
    fn from(message: &str) -> Self {
        OriginalError(message.to_string())
    }
}

fn do_something(flag: bool) -> Result<(), OriginalError> {
    if flag {
        Ok(())
    } else {
        Err(OriginalError::from("Something went wrong"))
    }
}

fn main() {
    match do_something(false) {
        Ok(_) => println!("Success!"),
        Err(e) => {
            let error_message = format!("Failed: {}", e);
            println!("{}", error_message);
        }
    }
}

エラーをネストしよう

問(リンク)

error::ErrorトレイトでsourceメソッドをAppErrorに実装することで?でエラーを伝播できるようにしましょう。

コード (詳細)
use std::error;
use std::fmt;
use std::io;
use std::string::FromUtf8Error;

#[derive(Debug)]
pub enum AppError {
    NotFound(io::Error),
    InvalidInput(FromUtf8Error),
    InternalError(Box<dyn error::Error>),
}

// Displayトレイトを実装してエラーメッセージを整える
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound(e) => write!(f, "Not Found: {}", e),
            AppError::InvalidInput(e) => write!(f, "Invalid Input: {}", e),
            AppError::InternalError(e) => write!(f, "Internal Error: {}", e),
        }
    }
}

// TODO: sourceメソッドを記載して、processの処理で?でエラーを伝播できるようにしてください。
impl error::Error for AppError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {}
}

// From<String> を実装して String からの変換を簡単に
impl From<io::Error> for AppError {
    fn from(err: io::Error) -> Self {
        AppError::NotFound(err)
    }
}

impl From<FromUtf8Error> for AppError {
    fn from(err: FromUtf8Error) -> Self {
        AppError::InvalidInput(err)
    }
}

impl From<String> for AppError {
    fn from(message: String) -> Self {
        AppError::InternalError(Box::new(io::Error::new(io::ErrorKind::Other, message)))
    }
}

// サンプル関数
fn do_something(input: &str) -> Result<(), AppError> {
    if input.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::NotFound,
            "Input cannot be empty",
        ))?
    } else if input == "404" {
        Err(AppError::from("Requested resource not found".to_string()))?
    } else if input == "500" {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Unexpected error occurred",
        ))?
    }
    Ok(())
}

// プロセス関数をResultでラップ
fn process() -> Result<(), AppError> {
    let inputs = vec!["", "404", "success"];

    for input in inputs {
        do_something(input)?; // エラーがあれば即座にreturn
        println!("Processed: {}", input);
    }

    Ok(())
}

fn main() {}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io;

    #[test]
    fn test_do_something_not_found() {
        let result = do_something("");
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.kind(), io::ErrorKind::NotFound);
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_do_something_internal_error() {
        let result = do_something("404");
        match result {
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            _ => panic!("Expected InternalError"),
        }
    }

    #[test]
    fn test_do_something_success() {
        let result = do_something("success");
        assert!(result.is_ok());
    }

    #[test]
    fn test_process() {
        let result = process();
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            Ok(_) => {} // 成功ケースは特にアサートしない
            _ => panic!("Unexpected error"),
        }
    }

    #[test]
    fn test_from_io_error() {
        let io_err = io::Error::new(io::ErrorKind::Other, "io error");
        let app_error: AppError = io_err.into();
        match app_error {
            AppError::NotFound(e) => {
                assert_eq!(e.to_string(), "io error");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_from_utf8_error() {
        let utf8_err = String::from_utf8(vec![0, 159]).unwrap_err();
        let app_error: AppError = AppError::from(utf8_err);
        match app_error {
            AppError::InvalidInput(e) => {
                assert!(e.to_string().contains("invalid utf-8"));
            }
            _ => panic!("Expected InvalidInput error"),
        }
    }

    #[test]
    fn test_from_string() {
        let app_error: AppError = String::from("custom error").into();
        match app_error {
            AppError::InternalError(e) => {
                assert_eq!(e.to_string(), "custom error");
            }
            _ => panic!("Expected InternalError"),
        }
    }
}

解答(リンク)

コード参照。

コード (詳細)
use std::error;
use std::fmt;
use std::io;
use std::string::FromUtf8Error;

#[derive(Debug)]
pub enum AppError {
    NotFound(io::Error),
    InvalidInput(FromUtf8Error),
    InternalError(Box<dyn error::Error>),
}

// Displayトレイトを実装してエラーメッセージを整える
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound(e) => write!(f, "Not Found: {}", e),
            AppError::InvalidInput(e) => write!(f, "Invalid Input: {}", e),
            AppError::InternalError(e) => write!(f, "Internal Error: {}", e),
        }
    }
}

// Errorトレイトを実装
impl error::Error for AppError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            AppError::NotFound(e) => Some(e),
            AppError::InvalidInput(e) => Some(e),
            AppError::InternalError(e) => Some(e.as_ref()),
        }
    }
}

// From<String> を実装して String からの変換を簡単に
impl From<io::Error> for AppError {
    fn from(err: io::Error) -> Self {
        AppError::NotFound(err)
    }
}

impl From<FromUtf8Error> for AppError {
    fn from(err: FromUtf8Error) -> Self {
        AppError::InvalidInput(err)
    }
}

impl From<String> for AppError {
    fn from(message: String) -> Self {
        AppError::InternalError(Box::new(io::Error::new(io::ErrorKind::Other, message)))
    }
}

// サンプル関数
fn do_something(input: &str) -> Result<(), AppError> {
    if input.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::NotFound,
            "Input cannot be empty",
        ))?
    } else if input == "404" {
        Err(AppError::from("Requested resource not found".to_string()))?
    } else if input == "500" {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Unexpected error occurred",
        ))?
    }
    Ok(())
}

// プロセス関数をResultでラップ
fn process() -> Result<(), AppError> {
    let inputs = vec!["", "404", "success"];

    for input in inputs {
        do_something(input)?; // エラーがあれば即座にreturn
        println!("Processed: {}", input);
    }

    Ok(())
}

fn main() {}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io;

    #[test]
    fn test_do_something_not_found() {
        let result = do_something("");
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.kind(), io::ErrorKind::NotFound);
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_do_something_internal_error() {
        let result = do_something("404");
        match result {
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            _ => panic!("Expected InternalError"),
        }
    }

    #[test]
    fn test_do_something_success() {
        let result = do_something("success");
        assert!(result.is_ok());
    }

    #[test]
    fn test_process() {
        let result = process();
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            Ok(_) => {} // 成功ケースは特にアサートしない
            _ => panic!("Unexpected error"),
        }
    }

    #[test]
    fn test_from_io_error() {
        let io_err = io::Error::new(io::ErrorKind::Other, "io error");
        let app_error: AppError = io_err.into();
        match app_error {
            AppError::NotFound(e) => {
                assert_eq!(e.to_string(), "io error");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_from_utf8_error() {
        let utf8_err = String::from_utf8(vec![0, 159]).unwrap_err();
        let app_error: AppError = AppError::from(utf8_err);
        match app_error {
            AppError::InvalidInput(e) => {
                assert!(e.to_string().contains("invalid utf-8"));
            }
            _ => panic!("Expected InvalidInput error"),
        }
    }

    #[test]
    fn test_from_string() {
        let app_error: AppError = String::from("custom error").into();
        match app_error {
            AppError::InternalError(e) => {
                assert_eq!(e.to_string(), "custom error");
            }
            _ => panic!("Expected InternalError"),
        }
    }
}

さいごに

もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。

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?