kdnakt blog

hello there.

Rust dojo第69回を開催した

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

kdnakt.hatenablog.com

[第69回の様子]

2023/03/08に第69回を開催した。インフルエンザでダウンしたので1週間空いてしまった...。

内容としてはRust By Example 日本語版19. 標準ライブラリの型の「19.8. Rc」、「19.9. Arc」に取り組んだ。
参加者は自分を入れて5人(だったはず)。

[学んだこと]

  • 19.8. Rc
  • Rcは参照カウンタ(reference counting)
  • 複数の所有権が必要な場合に利用できる
  • 参照が増えるとカウンタが1増える。ゼロになったら終わりでRc自体も参照されている値もドロップされる
  • Rcをクローンすると、ポインタが得られる:ディープコピーは発生しない
use std::rc::Rc;

fn main() {
    let rc_examples = "Rc examples".to_string();
    {
        println!("--- rc_a is created ---");
        
        // rc_examplesの所有権はrc_aにうつる
        let rc_a: Rc<String> = Rc::new(rc_examples);
        println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));
        // Reference Count of rc_a: 1
        
        {
            println!("--- rc_a is cloned to rc_b ---");
            
            // クローンするとそれぞれカウントが増える
            let rc_b: Rc<String> = Rc::clone(&rc_a);
            println!("Reference Count of rc_b: {}", Rc::strong_count(&rc_b));
            // Reference Count of rc_b: 2
            println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));
            // Reference Count of rc_a: 2
            
            // Rcの中の値が同じであればRcは等しい
            println!("rc_a and rc_b are equal: {}", rc_a.eq(&rc_b));
            
            // Rcの値のメソッドを直接呼び出すこともできる
            println!("Length of the value inside rc_a: {}", rc_a.len());
            // Length of the value inside rc_a: 11
            println!("Value of rc_b: {}", rc_b);
            // Value of rc_b: Rc examples
            
            println!("--- rc_b is dropped out of scope ---");
        }
        // rc_bがドロップされたのでカウントが1減る
        println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));
        // Reference Count of rc_a: 1
        
        println!("--- rc_a is dropped out of scope ---");
    }
    
    // rc_examplesの所有権はrc_aにうつっており、
    // rc_aがドロップされたのと同時にrc_examplesもドロップされているため、
    // ここでrc_examplesにアクセスすることはできない
}
  • rc_aのドロップと共に、元の値がドロップされていることを確認するために、次のような構造体を実装した
#[derive(PartialEq, Eq, Debug)]
struct A;

impl A {
    fn len(&self) -> i32 {
        0
    }
}

impl Drop for A {
    fn drop(&mut self) {
        println!("A is dropped!")
    }
}
  • これをrc_examplesに利用すると次のようになる
fn main() {
    let rc_examples = A {};
    {
        let rc_a: Rc<A> = Rc::new(rc_examples);
        println!("Reference Count of rc_a: {:?}", Rc::strong_count(&rc_a));

        // 省略

        println!("--- rc_a is dropped out of scope ---");
    }
    println!("Here rc_examples is already dropped with rc_a.");
}

// 出力結果は以下の通り:rc_aがドロップされるのと同じタイミングでrc_examplesもドロップされている
--- rc_a is created ---
Reference Count of rc_a: 1
--- rc_a is dropped out of scope ---
A is dropped!
Here rc_examples is already dropped with rc_a.
  • この参照カウンタ、次に出てくるArcがマルチスレッド用ということでいまいち用途がわかっていなかったが、ライフタイムの代わりに利用できるとのこと。
    • LeetCodeを解いている時にツリー構造を表す時にRcが出てきたのはそういうことだったのか
// ライフタイム表記あり
struct A<'a> {
    x: &'a i32,
    y: &'a i32,
}

// Rc利用バージョン
struct A {
    x: Rc<i32>,
    y: Rc<i32>,
}
  • 19.9. Arc
  • ArcはAtomicなRc(参照カウンタ)である
  • マルチスレッドで参照する場合はこちらを利用する
use std::time::Duration;
use std::sync::Arc;
use std::thread;

fn main() {
    //  この変数宣言で、値をヒープ上に配置する。
    let apple = Arc::new("the same apple");

    for _ in 0..10 {
        // クローンしているのはヒープ上の値への参照であるため、値が実際に複製されることはない。
        let apple = Arc::clone(&apple);

        thread::spawn(move || {
            // Arc が使われているので、Arcが所有する値を使ってスレッドをspawnすることができる。
            println!("{:?}", apple);
        });
    }

    // 全スレッドの終了を待機
    thread::sleep(Duration::from_secs(1));
}

// 実行結果は"the same apple"が10回出力されるだけなので省略
  • Arcを使わずに、Rcを利用すると、spawnに渡すラムダ式が変数をキャプチャできずコンパイルエラーが出る
use std::time::Duration;
use std::rc::Rc;
use std::thread;

fn main() {
    let apple = Rc::new("the same apple");

    for _ in 0..10 {
        let apple = Rc::clone(&apple);

        thread::spawn(move || {
            println!("{:?}", apple);
        });
    }

    thread::sleep(Duration::from_secs(1));
}

// 以下のコンパイルエラー
error[E0277]: `Rc<&str>` cannot be sent between threads safely
  --> src/main.rs:14:23
   |
14 |           thread::spawn(move || {
   |           ------------- ^------
   |           |             |
   |  _________|_____________within this `[closure@src/main.rs:14:23: 14:30]`
   | |         |
   | |         required by a bound introduced by this call
15 | |             // As Arc was used, threads can be spawned using the value allocated
16 | |             // in the Arc variable pointer's location.
17 | |             println!("{:?}", apple);
18 | |         });
   | |_________^ `Rc<&str>` cannot be sent between threads safely
   |
   = help: within `[closure@src/main.rs:14:23: 14:30]`, the trait `Send` is not implemented for `Rc<&str>`
note: required because it's used within this closure
  --> src/main.rs:14:23
   |
14 |         thread::spawn(move || {
   |                       ^^^^^^^
note: required by a bound in `spawn`
  --> /rustc/2c8cc343237b8f7d5a3c3703e3a87f2eb2c54a74/library/std/src/thread/mod.rs:709:1

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。
参照とかメモリ周りの話は難しい...。

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

github.com