所有権とは

完了

Rust には、メモリを管理するための所有権システムが含まれています。 コンパイル時、所有権システムでは一連のルールがチェックされ、所有権機能により、速度を低下することなくプログラムを実行できることが保証されます。

所有権について理解するために、まず、Rust の "スコープ ルール" と "移動セマンティクス" を見てみましょう。

スコープ ルール

Rust の場合、他のほとんどのプログラミング言語と同様に、変数は特定の "スコープ" 内でのみ有効です。 Rust では、スコープは多くの場合、中かっこ {} を使用して示されます。 一般的なスコープには、関数本体と、ifelsematch の分岐が含まれます。

Note

Rust では、"変数" は "バインド" と呼ばれることがよくあります。 これは、Rust の "変数" がそれほど可変ではないことが理由です。これらは、既定では変更できないため、多くの場合、変更されません。 代わりに、多くの場合、名前をデータに "結び付ける" と考えます。"バインド" という言い方はここから来ています。 このモジュールでは、"変数" と "バインド" という用語を同じ意味で使用します。

たとえば、スコープ内に定義されている文字列である mascot 変数があるとします。

// `mascot` is not valid and cannot be used here, because it's not yet declared.
{
    let mascot = String::from("ferris");   // `mascot` is valid from this point forward.
    // do stuff with `mascot`.
}
// this scope is now over, so `mascot` is no longer valid and cannot be used.

スコープの外で mascot を使用しようとすると、この例のようなエラーが表示されます。

{
    let mascot = String::from("ferris");
}
println!("{}", mascot);
    error[E0425]: cannot find value `mascot` in this scope
     --> src/main.rs:5:20
      |
    5 |     println!("{}", mascot);
      |                    ^^^^^^ not found in this scope

この例は、Rust Playground でオンラインで実行できます。

変数は、宣言されているポイントからそのスコープの末尾まで有効です。

所有権と削除

Rust では、スコープの概念にひねりを加えています。 オブジェクトは、スコープ外になると "削除" されます。変数を削除すると、関連付けられているすべてのリソースが解放されます。 ファイルの変数の場合、ファイルは閉じられることになります。 関連付けられたメモリが割り当てられている変数の場合、そのメモリは解放されます。

Rust において、バインドが削除されたときに解放される "関連付けられた" ものを含むバインドは、それらのものを "所有する" と言います。

上の例では、mascot 変数は、関連付けられた String データを所有しています。 String 自体で、その文字列の文字を保持するヒープ割り当てメモリが所有されます。 スコープの終わりで mascot は "削除" され、所有している String が削除されます。最後に、String が所有するメモリが解放されます。

{
    let mascot = String::from("ferris");
}
// mascot is dropped here. The string data memory will be freed here.

移動セマンティクス

場合によっては、変数に関連付けられているものがスコープの最後に削除されないようにしたいことがあります。 代わりに、バインド間で項目の所有権を移転することができます。

最も簡単な例は、新しいバインドを宣言する場合です。

{
    let mascot = String::from("ferris");
    // transfer ownership of mascot to the variable ferris.
    let ferris = mascot;
}
// ferris is dropped here. The string data memory will be freed here.

ここで重要なのは、所有権が移転されると、古い変数は有効でなくなることです。 上の例で、String の所有権を mascot から ferris に移転した後、mascot 変数は使用できなくなります。

Rust では、"所有権の移転" は "移動" と呼ばれます。 つまり、String 値の所有権は mascot から ferris移動されています。

Stringmascot から ferris に移動した後に mascot を使用しようとすると、コードはコンパイラでコンパイルされません。

{
    let mascot = String::from("ferris");
    let ferris = mascot;
    println!("{}", mascot) // We'll try to use mascot after we've moved ownership of the string data from mascot to ferris.
}
error[E0382]: borrow of moved value: `mascot`
 --> src/main.rs:4:20
  |
2 |     let mascot = String::from("ferris");
  |         ------ move occurs because `mascot` has type `String`, which does not implement the `Copy` trait
3 |     let ferris = mascot;
  |                  ------ value moved here
4 |     println!("{}", mascot);
  |                    ^^^^^^ value borrowed here after move

この結果は、"移動後の使用" というコンパイル エラーとして知られています。

重要

Rust では、一度に 1 つのものだけがデータを "所有" できます。

関数における所有権

引数として関数に渡される文字列の例を見てみましょう。 何かを関数に引数として渡すと、それが関数に "移動" します。

fn process(input: String) {}

fn caller() {
    let s = String::from("Hello, world!");
    process(s); // Ownership of the string in `s` moved into `process`
    process(s); // Error! ownership already moved.
}

コンパイラは、s 値が "移動" されたというエラーを返します。

    error[E0382]: use of moved value: `s`
     --> src/main.rs:6:13
      |
    4 |     let s = String::from("Hello, world!");
      |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
    5 |     process(s); // Transfers ownership of `s` to `process`
      |             - value moved here
    6 |     process(s); // Error! ownership already transferred.
      |             ^ value used here after move

前のスニペットでわかるように、process の最初の呼び出しにより、変数 s の所有権が移転されます。 コンパイラによって所有権が追跡されるので、process の 2 回目の呼び出しによりエラーが発生します。 リソースが移動された後は、以前の所有者を使用できなくなります。

このパターンは、Rust コードの記述方法に大きく影響します。 これは、Rust によって提示されるメモリの安全性の保証の中核です。

他のプログラミング言語では、関数に渡される前に、s 変数の String 値を暗黙的にコピーできます。 しかし、Rust では、このアクションは発生しません。

Rust では、所有権の移転 (つまり、移動) が既定の動作です。

移動せずにコピーする

前の例の (むしろ情報を提供している) コンパイラのエラー メッセージで、Copy 特性が言及されていることに気付いたかもしれません。 特性についてはまだ説明していませんが、Copy 特性を実装する値は移動されず、コピーされます。

Copy 特性を実装する値 u32 を見てみましょう。 次のコードは破損したコードと同じですが、問題なくコンパイルされます。

fn process(input: u32) {}

fn caller() {
    let n = 1u32;
    process(n); // Ownership of the number in `n` copied into `process`
    process(n); // `n` can be used again because it wasn't moved, it was copied.
}

数値のような単純型では、型を ''コピー'' します。 Copy 特性を実装します。つまり、移動されず、コピーされます。 ほとんどの単純型で同じアクションが発生します。 数値のコピーは低コストであるため、これらの値をコピーするのが理にかなっています。 文字列、ベクター、またはその他の複合型は、コピーするとコストが高くなる場合があるため、Copy 特性を実装せず、代わりに移動されます。

Copy を実装しない型をコピーする

前の例のエラーを回避する方法の 1 つは、移動前に型を明示的にコピーすることです。これは Rust で複製と呼ばれます。 .clone を呼び出すと、メモリが複製され、新しい値が生成されます。 新しい値は移動されるので、古い値を引き続き使用できます。

fn process(s: String) {}

fn main() {
    let s = String::from("Hello, world!");
    process(s.clone()); // Passing another value, cloned from `s`.
    process(s); // s was never moved and so it can still be used.
}

この方法は便利ですが、clone を呼び出すたびにデータの完全なコピーが作成されるため、コードの実行速度が低下するおそれがあります。 この方法には多くの場合、メモリ割り当てや他のコストが高い操作が含まれます。 "参照" を使用して値を "借用" する場合は、これらのコストを回避できます。 次のユニットでは、参照を使用する方法について学習します。