Rust dojo第41回を開催した

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

kdnakt.hatenablog.com

 

 

[第41回の様子]

2022/06/01に第41回を開催した。

 

内容としてはRust By Example 日本語版の「15. スコーピングの規則」、「15.1. RAII」に取り組んだ。

 

参加者は自分を入れて5人。人数が戻ってきて嬉しい。書かれてない内容にもいろいろ取り組んだせいか、その後社内Slackで関連する調べ物をしてる人もいてよかった。

 

今回はドライバの方がいろんな言語に詳しく、関数宣言自体もメモリに展開されるのとか、説明してくれてとても助かった。

 

[学んだこと]

  • 15. スコーピングの規則
  • 所有権とかライフタイムとかRust特有の概念とスコープの関係を見ていく
  • 15.1. RAII
  • RAIIはResource Acquisition Is Initializationの略
  • この原則によってスコープを抜けたオブジェクトはDropトレイトによるデストラクタが呼ばれてリソースを解放する
  • 例えばmain()関数の場合はこうなる
fn main() {
    let _box2 = Box::new(5i32);

    // その他の処理

    // ここで_box2は破棄されメモリが解放される
}
  • ネストしたブロックスコープがある場合は、ブロックを抜けると解放される
fn main() {
    // 他の処理
    {
        let _box3 = Box::new(4i32);

        // `_box3`はここで破棄され、メモリは解放される。
    }
    
    // 他の処理
}
  • スタックとヒープの違いを確認するために、{:p}フォーマットでポインタを出力してみる
// 出力される値は一例
fn main() {
    // 整数をヒープ上に確保
    let box2 = Box::new(5i32);
    println!("Address of box2 on stack: {:p}", &box2); // 0x7ffc92943f08
    println!("Address of *box2 on heap: {:p}", box2);  // 0x5578b8950ad0

    // ネストしたスコープ
    {
        // 整数をヒープ上に確保
        let box3 = Box::new(4i32);
        println!("Address of box3 on stack: {:p}", &box3); // 0x7ffc92943f98
        println!("Address of *box3 on heap: {:p}", box3);  // 0x5578b8950af0
        // `box3`はここで破棄され、メモリは解放される。
    }
}
  • スタック上にあるメモリ同士とヒープ上にあるメモリ同士のアドレスがそれぞれ近いのがわかる:また、ヒープが先に確保されるので、ヒープの方がポインタの番号が若い
  • 関数内の変数でも同じことを試してみると、連続して呼び出した場合は同じアドレスが使いまわされているのがわかる
fn create_box() {
    // 整数をヒープ上に確保
    let box1 = Box::new(3i32);
    println!("Address of box1 on stack: {:p}", &box1);
    println!("Address of *box1 on heap: {:p}", box1);
    // `box1`はここで破棄され、メモリは解放される。
}

fn main() {
    for _ in 0u32..2 {
        create_box(); // 2回create_box関数を呼び出す
    }
}

// 以下が出力される:アドレスは一例。1回目と2回目の呼び出しで結果が同じになる
Address of box1 on stack: 0x7ffc85eab330
Address of *box1 on heap: 0x55ced5080ad0
Address of box1 on stack: 0x7ffc85eab330
Address of *box1 on heap: 0x55ced5080ad0
  • Dropトレイトを利用して、オブジェクトが破棄される際の挙動を確認する(参考:Rust dojo第14回
struct ToDrop;

impl Drop for ToDrop {
    fn drop(&mut self) {
        println!("ToDrop is being dropped");
    }
}

fn main() {
    let to_drop1 = ToDrop;
    println!("Address of to_drop1 on stack: {:p}", &to_drop1); // 0x7fffeb8ad700
    let to_drop2 = Box::new(ToDrop);
    println!("Address of to_drop2 on stack: {:p}", &to_drop2); // 0x7fffeb8ad750

    // ToDropはフィールドを持たない構造体なので、実体をメモリ上に展開する必要がない。
    // それゆえ、Box::newは実際にはメモリ確保を行わず、仮のアドレス(手元では0x1)を使う。
    println!("Address of *to_drop2 on heap: {:p}    // Oops!", to_drop2); // 0x1
    
    // オブジェクト2個が破棄されるので「ToDrop is being dropped」が2回出力される
}
  • 値のない構造体のメモリ確保に関する挙動がちょっと面白かった

 

[まとめ]

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

スタックとヒープ、少しわかった気がする。

 

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

github.com