🌊

Rustにおける所有権の話

2021/03/29に公開

概要

Rustの中心的な機能として所有権があります。
今回は、その所有権について深堀りしていきます。

所有権について

プログラムとメモリ

  • プログラム実行中では、実行時にメモリを管理する必要があります。
  • 例えばJavaですと、定期的に使用されていないメモリを検索するガベージコレクションみたいなのがあったり、CやC++では、明示的にメモリを確保したり、解放したりしないといけません。

Rustでは、これを所有権という機能で管理します

所有権とは?

  • まず、型のインスタンスを作成し、変数に束縛するとメモリリソースが作成されます。
  • そのライフタイムに渡って、Rustコンパイラが検証します。
  • 束縛された変数は、リソースの所有者となります。これが所有権となります。

所有権によるメリット

  • どの部分のコードが、どのヒープ上のデータを使用しているかを把握できる
  • ヒープ上の重複するデータを最小化できる
  • メモリ不足にならないようにヒープ上の未使用のデータを排除できる

ちなみにスタックとヒープとは何か?

  • スタック領域とヒープ領域を一言で表すなら、プログラムの中で一時的に使用するメモリです。

スタック

  • コンパイラやOSが自動的に割り当て、解放を行います。
  • そのため、プログラムのタスクやスレッド、関数内の変数やアドレスに対して割り当てられます。

ヒープ

  • ヒープでは、メモリ領域を動的に確保、解放を繰り返すことができます。
  • また、メモリが必要になった際に確保を行う為、不要になった際には解放するという処理を自分自身で行わないといけません。
  • このヒープ領域は、プログラム実行時にOSからソフトウェアに対して一定量のヒープ領域が確保されます。

比較

  • スタックでは、使用するサイズが決定しているので、使いまくるとスタックでオーバーフローが発生する可能性があります。
  • ヒープでは、使用するサイズとタイミングは、アプリケーションで決定できるというメリットがあります。

所有権のルール

所有権のルールは下記の3つです。

  • Rustの各値は、所有者と呼ばれる変数と対応します。
  • どんな時でも、所有者は一つです。
  • 所有者がスコープから外れたら、値は破棄されます。

  • 下記の例ですと、Fooという構造体をインスタンス化して、変数fooに束縛してメモリリソースを作成しています。
  • この時の変数fooが、所有者です。
struct Foo {
    x: i32,
}

fn main() {
    let foo = Foo { x: 13 };
}

所有権とスコープ

  • Rustでは、スコープと呼ばれる概念があります。
  • スコープとは、要素が有効になるプログラム内の範囲のことを指します。
  • また、スコープの終わりをリソースの解放の場所として使用します。

例:

struct Foo {
    x: i32,
}

fn main() {
    let foo_1 = Foo { x: 13 };
    let foo_2 = Foo { x: 26 };

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

    println!("{}", foo_2.x);
    // foo_2 はここでドロップ
    // foo_1 はここでドロップ
}

[実行結果]

❯ cargo run
   Compiling variables v0.1.0 (/Users/yoshitaka.koitabashi/Desktop/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/variables`
13
26
  • 上記でfoo_2が先にドロップするのですが、これは何故でしょうか?
  • 答えは、スタックにあります。
  • スタックでは、LIFO方式となるため、この場合はfoo_2が先にドロップするのです。

構造体のドロップ

  • 構造体のドロップは少し変わっていて、構造体がドロップされた場合、まず構造体自体がドロップされ、次にその子要素が個別に削除されます。

例:

struct Bar {
    x: i32,
}

struct Foo {
    bar: Bar,
}

fn main() {
    let foo = Foo { bar: Bar { x: 13 } };
    println!("{}", foo.bar.x);
    // foo が最初にドロップ
    // 次に foo.bar がドロップ
}

まとめ

  • Rustでは、所有権という機能によりメモリ管理を行っています。
  • メモリを所有している変数がスコープを抜けたら、メモリは自動的に返還される。この仕組みは、今まで無かった(ガベージコレクタがある言語では、使用されないメモリを検知して片付ける。CやC++のような言語では、メモリがもう使用されないことを見計らって、明示的に返還するコードを呼び出す必要がありました。)

参考文献

Discussion