電気ひつじ牧場

技術メモ

Rustにっき/4日目・所有権

4日目はRustを特徴付ける概念・所有権についてです。

安全なコーディング

Rustは以下の2つを保証します。

  1. プログラマが、値の生存期間すなわち値に属するメモリの取得、解放のタイミングをコントロールできる。
  2. プログラムが解放済みのオブジェクトに対するポインタを使わない。

これらは両方とももぬるぽメモリリークを起こさないために重要な事です。他の言語でこれらの項目を見て見るとこうなります。

言語 1の項目 2の項目
Java, Python NG。解放はGC任せ。 OK。あるオブジェクトに到達できるポインタが無くならない限りオブジェクトは解放されないため。
C, C++ OK。delete, freeでいつでも解放できる NG。ローカル変数へのポインタをreturnするなど

Rustはポインタの使われ方に対して制約を与える事で、これら2つの項目が満たされるようにしました。これによって、メモリ管理にまつわるバグ、セキュリティホールを減らし、安全な並列プログラミングを行うことができます。

所有権

Rustは全ての値がその生存期間を決定する唯一の所有者を持ちます。所有者がスコープを抜けるなどして解放される時に同時に所有していた値も自動的に解放します。唯一の所有者というのが肝心で、これによりリソース解放のタイミングが判断しやすくなります。

fn sample() {
    let myVec = vec![2,3,4]; // メモリの確保
} 
// リソースもここで解放される

上の例では、ベクタvec![2,3,4];の所有者はその変数であるmyVecです。制御がsample()を抜けるとmyVecは解放(ドロップという)されるのでヒープ上に確保されていたバッファも同時に解放されます。

所有者になれるものは変数だけではありません。

所有者の種類 所有するもの
変数 変数の値
配列、ベクタ 要素
構造体 フィールド

もう少し複雑な例です。

struct Person {name: String, age: i32};
let mut group = Vec::new();
group.push(Person {name: "foobar".to_string(), age: 18});
group.push(Person {name: "hogehoge".to_string(), age: 18});
group.push(Person {name: "powwow".to_string(), age: 18});

先ほどの表を参考に所有関係を矢印で表すと、変数group→ベクトル→ベクトルの要素(Person構造体)→構造体のフィールド→(nameフィールドは)文字列のようになります。面倒なので図にはしませんがこれは木構造になります。根は変数になっていますね。この変数がドロップされると再帰的に木全体がドロップされます。

移動

Rustでは大抵の場合変数への代入や関数への引数の受け渡しの際に値がコピーされず、所有権の移動が行われ、代入元の変数は未初期化の状態になります。

えってなりませんか?僕はなりました。要するに別の変数に代入すると元の変数は死ぬということです。そこいらの言語の代入とはわけが違うのです。しかし、これのおかげで値をディープコピーすることなく値に対する唯一の所有権というルールが守られます。

それを実際確かめてみましょう。先ほど出てきたベクタをループで名前を表示させます。

struct Person {name: String, age: i32};
let mut group = Vec::new();
group.push(Person {name: "foobar".to_string(), age: 18});
group.push(Person {name: "hogehoge".to_string(), age: 18});
group.push(Person {name: "powwow".to_string(), age: 18});

for mut p in group {
    println!("{}", p.name);
}

普通にnameが表示されます。ではこのコードの後にもう一度全く同じfor文を書いてみます。

/** 省略 **/

// 以下を追加
for mut p in group {
    println!("{}", p.name);
}

コンパイルします。

24 | for mut p in group {
   |     ----^
   |     |
   |     help: remove this `mut`

error[E0382]: use of moved value: `group`
  --> src/lib.rs:24:14
   |
20 | for mut p in group {
   |              ----- value moved here
...
24 | for mut p in group {
   |              ^^^^^ value used here after move
   |
   = note: move occurs because `group` has type `std::vec::Vec<main::Person>`, which does not implement the `Copy` trait

エラーになりました。これはfor..in構文が内部の変数にベクタを移動させるため、1度目のループでgroupは未初期化状態になります。そのため、2回目のループではこのベクタを渡すことはできません。

いやいやこれじゃ流石に不便だろうと思いますが、rustでは参照を使って上手くやります。続きは次回です。

続く

Rust、面白くなってきましたね。今日は移動という全く新しい概念を勉強して感動しました。それはさておき記述量が少ないのは日中バイトでLaravelやらVue.jsやらいじって疲れてるからです・・・。