Rust dojo第42回を開催した

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

kdnakt.hatenablog.com

 

 

[第42回の様子]

2022/06/08に第42回を開催した。

 

内容としてはRust By Example 日本語版の「15.2. 所有権とムーブ」、「15.2.1. ミュータビリティ」、「15.2.2. Partial moves」に取り組んだ。

 

参加者は自分を入れて6人。

社内イベントでRust dojoについて紹介した直後だったから、新しい人来るかなーと思ったけどそんなことはなかった。

 

[学んだこと]

  • 15.2. 所有権とムーブ
  • 前回見たように、Rustの変数は自身が確保した資源を解放する責任がある:そのため、1つの資源を確保できるのは1人だけとなる
    • ただし、全ての変数が資源を保有するわけではない
  • 資源の所有権は変数のアサインや関数の引数への値渡しによってムーブする
    • その際、元の所有者は資源を利用できなくなる
fn main() {
    let a = Box::new(5i32);

    println!("a contains: {}", a);

    // 変数をアサインしてaをbにムーブ
    let b = a;

    // aは所有権を持たないため、データにアクセスできない
    println!("a contains: {}", a);
}

// コンパイルエラー
error[E0382]: borrow of moved value: `a`
  --> src/main.rs:41:32
   |
25 |     let a = Box::new(5i32);
   |         - move occurs because `a` has type `Box<i32>`, which does not implement the `Copy` trait
...
31 |     let b = a;
   |             - value moved here
...
41 |     println!("a contains: {}", a);
   |                                ^ value borrowed here after move
  • コンパイルエラーメッセージにあるように、Copyトレイトが実装されているu32の場合は、変数をアサインしても所有権が写らない
fn main() {
    let x = 5u32;

    // xをyにコピー
    let y = x;

    // 両方の値はそれぞれ独立して利用可能
    println!("x is {}, and y is {}", x, y);
    // x is 5, and y is 5
}
  • 関数の引数の値渡しの場合も以下のようになる
fn destroy_box(c: Box<i32>) {
    println!("Destroying a box that contains {}", c);

    // cのメモリはここで解放される
}

fn main() {
    let a = Box::new(5i32);

    let b = a;

    // 所有権がbから関数に移る
    destroy_box(b);

    println!("b contains: {}", b);
}

// コンパイルエラー
error[E0382]: borrow of moved value: `b`
  --> src/main.rs:55:32
   |
31 |     let b = a;
   |         - move occurs because `b` has type `Box<i32>`, which does not implement the `Copy` trait
...
47 |     destroy_box(b);
   |                 - value moved here
...
55 |     println!("b contains: {}", b);
   |                                ^ value borrowed here after move

  • 15.2.1. ミュータビリティ
  • データの所有権が移ると同時にミュータビリティを変更できる
    • 不変だった変数を可変にすることもできる
fn main() {
    let immutable_box = Box::new(5u32);
    
    println!("immutable_box contains {}", immutable_box); // 5
    
    //所有権とミュータビリティを変更
    let mut mutable_box = immutable_box;
    println!("mutable_box contains {}", mutable_box); // 5
    
    *mutable_box = 4;
    println!("mutable_box now contains {}", mutable_box); // 4
}
  • サイトの例では変数のアサインだけだったので、関数の引数への値渡しのパターンも試してみた
fn destroy_box(mut c: Box<u32>) {
    // 3に書き換える
    *c = 3u32;
    println!("Destroying a box that contains {}", c);
}

fn main() {
    let immutable_box = Box::new(5u32);

    println!("immutable_box contains {}", immutable_box);

    destroy_box(immutable_box);
}

// 出力結果は以下の通り
immutable_box contains 5
Destroying a box that contains 3
  • 15.2.2. Partial moves
  • 分解代入をする際にも、同じように所有権をムーブできる
    • refキーワードを利用して参照を取ることで、部分的な所有権の移動も可能
#[derive(Debug)]
struct Person {
    name: String,
    age: u8,
}

fn main() {
    let person = Person {
        name: String::from("Alice"),
        age: 20,
    };

    // nameはムーブ、ageは参照
    let Person { name, ref age } = person;

    println!("The person's age is {}", age); // 20
    println!("The person's name is {}", name); // Alice

    // personは部分的にムーブしているため、以下はコンパイルエラー
    //println!("The person struct is {:?}", person);

    // person.ageは所有権が残っているので利用可能
    println!("The person's age from person struct is {}", person.age);
}

// 部分ムーブ時のコンパイルエラー
error[E0382]: borrow of partially moved value: `person`
  --> src/main.rs:21:43
   |
14 |     let Person { name, ref age } = person;
   |                  ---- value partially moved here
...
21 |     println!("The person struct is {:?}", person);
   |                                           ^^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `person.name` has type `String`, which does not implement the `Copy` trait

 

[まとめ]

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

なんとなくわかったつもりになっていた所有権とムーブ。部分的なムーブもできたんだな...。

 

プルリクエストはそのうち追記します。

 

[2022/06/15追記]

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

github.com