kdnakt blog

hello there.

Rust dojo第60回を開催した

第60回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第60回の様子]

2022/12/07に第60回を開催した。

内容としてはRust By Example 日本語版の18. エラーハンドリングの「18.4.3. エラーをBoxする」〜「18.4.4. ?の他の活用法」に取り組んだ。

参加者は自分を入れて5人。師走で忙しいけどたくさん集まってよかった。

 

[学んだこと]

  • 18.4.3. エラーをBoxする
  • 前回は独自にDoubleErrorというエラー型を定義してOptionとParseIntErrorをまとめて扱う方法を学んだ
  • ただ、元のエラー情報が失われているので、今回はBox型を利用して元のエラーを維持する方法を見ていく
  • OptionはErrorトレイトを実装していないので、Errorトレイトを実装した独自の例外を定義する
use std::error;
use std::fmt;

// BoxでErrorをラップしたエイリアスを定義
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

// OptionがNoneの時の例外を定義
#[derive(Debug, Clone)]
struct EmptyVec;
impl fmt::Display for EmptyVec {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "invalid first item to double")
    }
}
impl error::Error for EmptyVec {}

// 最初の要素を2倍して返す関数
fn double_first(vec: Vec<&str>) -> Result<i32> {
    vec.first()
        .ok_or_else(|| EmptyVec.into()) // Boxに変換
        .and_then(|s| {
            s.parse::<i32>()
                .map_err(|e| e.into()) // Boxに変換
                .map(|i| 2 * i)
        })
}

fn print(result: Result<i32>) {
    match result {
        Ok(n) => println!("The first doubled is {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

fn main() {
    let numbers = vec!["42", "93", "18"];
    let empty = vec![];
    let strings = vec!["tofu", "93", "18"];

    print(double_first(numbers)); // 82
    print(double_first(empty));   // invalid first item to double
    print(double_first(strings)); // invalid digit found in string
}
  • OptionをBoxに変換する際にok_or()ではなくok_or_else()が使われているのは、Boxを使うことでの余計な動的メモリ確保を避けるため
  • 18.4.4. ?の他の活用法
  • double_first()の実装を?を使ってさらにシンプルにできる
// 前の例と同じ
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

// ?で内部の値をその場で取得
fn double_first(vec: Vec<&str>) -> Result<i32> {
    let first = vec.first().ok_or(EmptyVec)?;
    let parsed = first.parse::<i32>()?;
    Ok(2 * parsed)
}
  • ?演算子Err(From::from(err))を実行してくれる
    • 上の例だと、EmptyVecの値を受け取って、Box<dyn error::Error>に変換している
  • 別の書き方として以下のような方法もあるがやや煩雑
fn double_first(vec: Vec<&str>) -> Result<i32> {
    let first = vec.first()
        // Box::from(EmptyVec)だとerror[E0283]: type annotations neededというコンパイルエラーになる 
        .ok_or_else(|| Box::<dyn error::Error>::from(EmptyVec))?;
    let parsed = first.parse::<i32>()?;
    Ok(2 * parsed)
}

 

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。

久しぶりにメモリ確保に関する話が出てきて難しかった。

 

今週のプルリクエストはこちら。

github.com