Rustの所有権
所有権(ownership)は、Rust を Rust たらしめている考え方です。ガベージコレクタなしで、しかもメモリを手動で解放することなしに、メモリを安全に扱えるのは所有権のおかげです。最初は少し変わった仕組みに感じるかもしれません。でも、それはみんな同じです。中心となる考え方はシンプルなので、一歩ずつ積み上げていきましょう。
所有権が解決する問題
Section titled “所有権が解決する問題”プログラムは、実行中にデータをメモリに置きます。使い終わったメモリはどこかで片付けなければいけません。片付けないと、使わないメモリがどんどんたまっていき(これを「メモリリーク」と呼びます)、やがてプログラムが落ちてしまいます。多くの言語は、次の2つのどちらかの方法でこれを解決しています。
- 手動で解放する(C/C++):メモリを解放するのはあなたです。解放し忘れるとメモリリークになり、早く解放しすぎるとクラッシュやセキュリティ上のバグにつながります。
- ガベージコレクタ(Java、Go、Python):裏で動く仕組みが自動的に片付けてくれます。楽ですが、その分パフォーマンスが落ち、動きも予測しにくくなります。
Rust は3つ目の道を選びました。所有権のルールにもとづいて、それぞれの値をいつ片付けるべきかをコンパイル時に正確に決めてしまうのです。手動での解放も、ガベージコレクタも必要ありません。
所有権の3つのルール
Section titled “所有権の3つのルール”所有権の仕組みは、この3つのルールがすべてです。ほかのことはすべて、ここから導かれます。
- それぞれの値には、所有者(owner)がひとりだけいます。所有者とは、その値を「持っている」変数のことです。
- 所有者は、同時にひとりだけです。
- 所有者がスコープの外に出ると、その値は自動的に片付けられます。
それでは、ルールをひとつずつ動かしながら見ていきましょう。
ルール3:スコープが終わると値は破棄される
Section titled “ルール3:スコープが終わると値は破棄される”スコープとは、{ から } までの範囲のことです。変数のスコープが終わると、Rust はその値を自動的に解放します。free() のようなコードを書く必要はありません。
fn main() { { let message = String::from("こんにちは"); // ここでは message が有効 println!("{message}"); } // <- スコープが終わり、`message` は自動的に片付けられる
// println!("{message}"); // ❌ エラー:ここにはもう message は存在しない}あなたは何も解放していません。message がスコープの外に出た瞬間に、Rust が代わりに片付けてくれたのです。
ルール1と2:「ムーブ」
Section titled “ルール1と2:「ムーブ」”ここからがおもしろいところです。ある変数を別の変数に代入すると何が起きるか、見てみましょう。
fn main() { let s1 = String::from("こんにちは"); let s2 = s1; // 所有権が s1 から s2 へ「ムーブ」する
println!("{s2}"); // ✅ 動く // println!("{s1}"); // ❌ エラー:s1 はムーブ済み}let s2 = s1 と書いても、Rust は文字列をコピーしません。所有権を s1 から s2 へムーブ(move、移動)します。家の鍵をイメージしてください。合鍵を作るのではなく、鍵そのものを相手に手渡すのです。ルール2によって所有者は同時にひとりだけなので、鍵を手放した s1 はもう使えません。使おうとすると、次のエラーになります。
error[E0382]: borrow of moved value: `s1`これは「ムーブされた値 s1 を使おうとしています」という意味です。値はもう s2 に渡したので、s1 からは触れない、とコンパイラが教えてくれています。
コピーが2つほしいときは?
Section titled “コピーが2つほしいときは?”本当に、元とは別の独立したコピーがほしいときは .clone() を呼びます。
fn main() { let s1 = String::from("こんにちは"); let s2 = s1.clone(); // 中身を丸ごとコピーする
println!("s1 = {s1}, s2 = {s2}"); // ✅ これで両方とも使える}clone() をわざわざ書かせるのには理由があります。コピーは重い処理になることがあるので、本当に必要なときだけ、あなたが明示的に頼むようになっているのです。
単純な型はムーブではなくコピーされる
Section titled “単純な型はムーブではなくコピーされる”では、なぜ次のコードはエラーにならないのでしょうか。
fn main() { let x = 5; let y = x; println!("x = {x}, y = {y}"); // ✅ 両方とも使える}整数、浮動小数点数、真偽値、char のように、小さくてサイズが決まっている値はコピーのコストがとても小さいので、Rust はムーブせずにそのままコピーします。初心者のうちは、次の目安で覚えておけば大丈夫です。
- 単純な値(数値、
bool、char)→ コピーされる。元の変数もそのまま使える。 - 所有権を持つデータ(
Stringなど)→ ムーブされる。元の変数は使えなくなる。
所有権と関数
Section titled “所有権と関数”値を関数に渡したときも、変数に代入したときと同じようにムーブが起きます。
fn take_ownership(s: String) { println!("今は私が所有者です: {s}");} // ここで s は破棄される
fn main() { let text = String::from("こんにちは"); take_ownership(text); // println!("{text}"); // ❌ エラー:text は関数へムーブ済み}関数に渡すたびに値が使えなくなるのでは不便ですよね。そこで登場するのが、次のテーマである 借用(borrowing)です。借用を使うと、関数は所有権を受け取らずに値を使うだけで済むので、呼び出したあとも元の変数を使い続けられます。
まずは自分で解いてみましょう。Rust Playground でも、手元のプロジェクトでもかまいません。途中でコンパイルエラーが出るのは当たり前です。エラーメッセージを読んでみてください。たいていはムーブが起きた場所をそのまま指し示してくれます。
1. 元の文字列も残したい
Section titled “1. 元の文字列も残したい”次のプログラムはコンパイルできません。どちらの変数も消さずに、下書き -> 下書き v2 と表示されるように直してください。
fn main() { let original = String::from("下書き"); let mut edited = original; edited.push_str(" v2"); println!("{original} -> {edited}");}error[E0382]: borrow of moved value: `original` --> src/main.rs:5:16 |2 | let original = String::from("下書き"); | -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait3 | let mut edited = original; | -------- value moved here4 | edited.push_str(" v2");5 | println!("{original} -> {edited}"); | ^^^^^^^^ value borrowed here after moveエラーは「ムーブ済みの値 original を借用しようとしている」という意味です。
ヒント:片方を変更してももう片方に影響しない、独立した2つの文字列が必要です。
解答を見る
fn main() { let original = String::from("下書き"); let mut edited = original.clone(); // 完全に別のコピーを作る edited.push_str(" v2"); println!("{original} -> {edited}");}実行結果:
下書き -> 下書き v2let mut edited = original; と書くと文字列がムーブされるので、original はもう使えません。.clone() を使えば、専用のメモリを持つ2つ目の文字列ができます。これで push_str が変更するのは edited だけになり、original は 下書き のまま残ります。
2. 受け取って、返す
Section titled “2. 受け取って、返す”String の所有権を受け取り、末尾に " -- Ferris" を付け足して、その文字列を返す関数 add_signature を書いてください。main から呼び出して、結果を表示しましょう。
ヒント:関数は受け取った値をそのまま返せます。シグネチャに -> String と書き、引数は変更できるように mut text: String として受け取ります。
解答を見る
fn add_signature(mut text: String) -> String { text.push_str(" -- Ferris"); text // 所有権が呼び出し元へ戻っていく}
fn main() { let letter = String::from("またね!"); let letter = add_signature(letter); println!("{letter}");}実行結果:
またね! -- Ferris所有権は呼び出しと一緒に関数の中へムーブし、戻り値と一緒に外へムーブして戻ってきます。それを新しい変数 letter で受け取っています(古い letter をシャドーイングしています)。これでも動きますが、毎回値を渡しては返してもらうのは面倒ですよね。まさにこの問題を解決するのが借用です。
3. 今の所有者は誰?
Section titled “3. 今の所有者は誰?”次のプログラムはコンパイルできません。Anaがリストに加わりました と表示し、続けて ["Ana"] と表示されるように直してください。
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); names.push(name); println!("{name}がリストに加わりました"); println!("{:?}", names);}error[E0382]: borrow of moved value: `name` --> src/main.rs:5:16 |3 | let name = String::from("Ana"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait4 | names.push(name); | ---- value moved here5 | println!("{name}がリストに加わりました"); | ^^^^ value borrowed here after moveヒント:push も、関数呼び出しと同じように所有権を受け取ります。4行目のあと、文字列の所有者は誰でしょうか。ムーブの前に name を使うか、新しい所有者に聞いてみましょう。
解答を見る
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); println!("{name}がリストに加わりました"); // まだ所有しているうちに使う names.push(name); // ここからはベクタが所有者 println!("{:?}", names);}実行結果:
Anaがリストに加わりました["Ana"]names.push(name) のあとはベクタが所有者になるので、name はもう使えません。先に表示するのがひとつの解決策です。元の順番のまま、代わりに names[0] を表示する方法もあります。ベクタがまだその文字列を持っているからです。クローンしても動きますが、ここでわざわざコピーする必要はありません。
- 所有権のおかげで、Rust はガベージコレクタなしでメモリを安全に管理できる。
- 3つのルール:値の所有者はひとり、所有者は同時にひとりだけ、所有者がスコープを出たら値は破棄される。
- 所有権を持つデータ(
Stringなど)を代入するとムーブされ、元の変数は使えなくなる。 - 独立したコピーがほしいときは
.clone()を使う。単純な型(数値、bool、char)は自動的にコピーされる。 - 値を関数に渡してもムーブが起きる。これを解決するのが次の借用。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. Rust では、ひとつの値は同時に何人の所有者を持てますか?
2. String をムーブせずに、独立したコピーを作るには何というメソッドを呼びますか?
3. 別の変数に代入したとき、ムーブではなく自動的にコピーされる型はどれですか?
4. String を値渡し(参照ではなく)で受け取る関数に渡すと、どうなりますか?
5. このページの説明によると、Rust が値を自動的に片付ける(破棄する)のはいつですか?
スコア: 0 / 5
さらに詳しく
Section titled “さらに詳しく”- 所有権とは? — The Rust Programming Language 日本語版:公式の解説です。
Stringがメモリ上でどうなっているか、なぜムーブではコピーが起きないのかを図で説明しています。 - 所有権とムーブ — Rust By Example 日本語版:関数へのムーブも含めた、短くて実行できるムーブの例です。
- RAII — Rust By Example 日本語版:値がスコープを出たときにどう片付けられるのか、そして
Dropトレイトがどう関わるのかを紹介しています。 Copy— 標準ライブラリのドキュメント(英語):ムーブではなくコピーされるのはどの型か、そしてCopyとCloneの違いがわかります。- ExercismのRustトラック(英語):このページより多くの問題に挑戦したくなったら、フィードバック付きの無料の練習問題がおすすめです。