saitoxu.io

AboutTwitterGitHub

Rustのメモリ安全性を支える3つの概念

May 27, 2017

Rust のメモリ安全性を支える 3 つの概念、 所有権借用ライフタイム について自分なりにざっくりまとめてみました。

まだ学習途中であるため、間違いがあるかもしれません。 そのときは Twitter か何かでご指摘頂けると幸いです。

所有権

Rust ではリソースに対して同時に 1 人の所有者しか保持できず、 その権利を 所有権 といいます。

たとえば以下のコードを考えてみます。

let v = vec![1, 2, 3];

let v2 = v;

このとき、もともとvが持っていたベクタのリソースに対する 所有権がv2に移る(ムーブする)ことを表しています。

所有権を失効した変数はスコープ内であっても使えません。 これにより Rust はリソースに対して同時に 1 つの参照しかないことを保証し、 ダングリングポインタ(不正なメモリ領域を指すポインタ)の発生を防いでいます。

関数の仮引数に渡すときも、所有権は仮引数に移ることになります。 元の変数は使うことはできません。

fn take(v: Vec<i32>) {
    println!("take {}", v[0]);
}

let v = vec![1, 2, 3];

take(v);

println!("{}", v[0]); // Error: use of moved value: `v`

ただし、プリミティブ型に代表されるCopyトレイトを実装した型は 所有権を移してもリソースがコピーされるので元の変数が使えます。

ちなみにトレイト*1は「その型が実装しなければならないメソッドの集まり・実装」のことです。

借用

借用は所有権を移すことなく、リソースを一時的に借りることをいいます。

コンパイル時に借用チェッカが、借用による参照先のオブジェクトが有効であるかを検査します。

借用には 2 種類あって、イミュータブルな& Tとミュータブルな&mut Tがあります。

fn take(v: &Vec<i32>) {
    println!("take {}", v[0]);
}

let v = vec![1, 2, 3];

take(&v);

println!("{}", v[0]); // 使える

また、借用には以下のルールがあります。

  • 所有者のスコープより長く存続してはいけない
  • 以下の 2 種類のどちらか 1 つを持つことはあるが、両方を同時には持てない
    • リソースに対する 1 つ以上の参照(&T
    • ただ 1 つのミュータブルな参照(&mut T

ライフタイム

ライフタイムは借用の基礎となる概念で、借用した参照の有効期限のことをいいます。 参照の有効期限を設定することで、 リソース解放後の参照などのエラーをコンパイル時に検出できるようになります。

ライフタイムは以下のように指定します。

fn bar<'a>(x: &'a i32) {
}

'aをライフタイム a と呼びます。 関数だけでなく、構造体が参照を持つ場合にもライフタイムを指定する必要があります。

ライフタイムを越えた参照を使うことはできません。

struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let x;

    {
        let y = &5;
        let f = Foo { x: y };
        x = &f.x; // fがxより先にスコープから抜けてしまうので、コンパイルエラーが起きる
    }

    println!("{}", x);
}

おわりに

所有権・借用・ライフタイムについてざっくりまとめました。 特にライフタイムの理解が難しく、まだ全然使えそうにないです。 今後も継続して勉強していきます。

関係ないですが、この「Rust のよいところを教えてください」を 読むと学ぶモチベーションになります。

*1 トレイト - Wikipedia (https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88)


© 2021, Yosuke Saito