kdnakt blog

hello there.

Rust dojo第62回を開催した

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

kdnakt.hatenablog.com

 

 

[第62回の様子]

2022/12/21に第62回を開催した。

内容としてはRust By Example 日本語版の18. エラーハンドリングの「18.5. Resultをイテレートする」、19. 標準ライブラリの型の「19.1. Box, スタックとヒープ」に取り組んだ。

参加者は自分を入れて多分5人。最後まで参加してくれてありがとうございます。

 

[学んだこと]

  • 18.5. Resultをイテレートする
  • 先週はResult.ok()filter_map()の組み合わせで成功した結果だけをベクタにまとめる方法を学んだ
  • 今回は反対にcollect()の戻り値をResult型に指定することで、エラーを見つけ次第処理を終了させる
fn main() {
    let strings = vec!["tofu", "93", "18"];
    // 前回はVec<i32>だった型が変わっている
    let numbers: Result<Vec<_>, _> = strings
        .into_iter()
        .map(|s| s.parse::<i32>())
        .collect();
    println!("Results: {:?}", numbers);
    // Results: Err(ParseIntError { kind: InvalidDigit })
}

  • 成功した結果と失敗した結果を別々のベクタに詰めて返すにはpartition()を利用する
fn main() {
    let strings = vec!["tofu", "93", "18"];
    let (numbers, errors): (Vec<_>, Vec<_>) = strings
        .into_iter()
        .map(|s| s.parse::<i32>())
        .partition(Result::is_ok);
    println!("Numbers: {:?}", numbers);
    // Numbers: [Ok(93), Ok(18)]
    println!("Errors: {:?}", errors);
    // Errors: [Err(ParseIntError { kind: InvalidDigit })]
}
  • このままだとResult型に包まれた値になっっていて扱いづらいので、Result::unwrapResult::unwrap_errを用いて扱いやすくする
fn main() {
    // 省略
    let numbers: Vec<_> = numbers.into_iter().map(Result::unwrap).collect();
    let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect();
    println!("Numbers: {:?}", numbers);
    // Numbers: [93, 18]
    println!("Errors: {:?}", errors);
    // Errors: [ParseIntError { kind: InvalidDigit }]
}
  • 19.1. Box, スタックとヒープ
  • Rustでは全ての値はデフォルトでスタックに割り当てられる
  • ヒープ上に割り当てるにはBoxを使う
    • ボックスがスコープを抜けると自動的にデストラクタが呼ばれヒープが解放される
use std::mem;

#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

#[allow(dead_code)]
struct Rectangle {
    top_left: Point,
    bottom_right: Point,
}

fn origin() -> Point {
    Point { x: 0.0, y: 0.0 }
}

fn boxed_origin() -> Box<Point> {
    Box::new(Point { x: 0.0, y: 0.0 })
}

fn main() {
    // この変数ははすべてスタック上に割り当てられる。
    let point: Point = origin();
    let rectangle: Rectangle = Rectangle {
        top_left: origin(),
        bottom_right: Point { x: 3.0, y: -4.0 }
    };

    // ヒープ上に割り当てられたRectangle
    let boxed_rectangle: Box<Rectangle> = Box::new(Rectangle {
        top_left: origin(),
        bottom_right: Point { x: 3.0, y: -4.0 },
    });

    let boxed_point: Box<Point> = Box::new(origin());

    let box_in_a_box: Box<Box<Point>> = Box::new(boxed_origin());

    println!("Point occupies {} bytes on the stack",
             mem::size_of_val(&point));
             // 16バイト(f64は64ビット=8バイトなので、Pointは8+8=16)
    println!("Rectangle occupies {} bytes on the stack",
             mem::size_of_val(&rectangle));
             // RectangleはPointが2つなので16+16=32バイト

    // ボックスのサイズはポインタのサイズに等しい
    println!("Boxed point occupies {} bytes on the stack",
             mem::size_of_val(&boxed_point));
             // ポインタは8バイト(64ビットOSの場合)
    println!("Boxed rectangle occupies {} bytes on the stack",
             mem::size_of_val(&boxed_rectangle));
             // こちらも8バイト
    println!("Boxed box occupies {} bytes on the stack",
             mem::size_of_val(&box_in_a_box));
             // こちらも8バイト

    // `boxed_point`の保持するデータを`unboxed_point`にコピーする
    let unboxed_point: Point = *boxed_point;
    println!("Unboxed point occupies {} bytes on the stack",
             mem::size_of_val(&unboxed_point));
             // ヒープ上にあるPointなので16バイト
}

 

[まとめ]

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

1年前はよくわかってなかったヒープとスタックの話もすんなり理解できた(気がする)。今年も1年間へっぽこエンジニアの勉強会にお付き合いくださった同僚の方々、ありがとうございます。

 

今週はあまり元のコードを変更しなかったのでプルリクエストなし。