【Rust】メモリ管理:Copy と Clone から理解するスタックとヒープ
Rust の最初の難所である「所有権」について「なぜ代入しただけで変数が使えなくなるものと使えるものがあるのか?」「CopyとCloneは何が違うのか?」
これらの疑問を解消するには、メモリのスタックとヒープの挙動を理解するのが近道です。
本記事では、Rust がどのようにメモリを扱っているかを図解しつつ、CopyトレイトとCloneトレイトの違いについて解説します。
1. スタックとヒープの基礎
プログラムがメモリを使用する際、大きく分けて「スタック」と「ヒープ」という 2 つの領域を使います。
スタック領域(Stack): 「一時的で高速」
- 特徴: LIFO(Last In, First Out / 後入れ先出し)。
- 用途: 関数の実行手順、引数、ローカル変数など。
- 速度: メモリの確保・解放はポインタを移動させるだけなので非常に高速。
- 制約: コンパイル時にサイズが決定しているデータ(固定長)しか置けない。
ヒープ領域(Heap): 「半永続的で可変」
- 特徴: 必要なサイズを動的に確保し、その場所(アドレス)へのポインタをスタックに渡す。
- 用途: 文字列(String)やベクタ(Vec)など、サイズが可変のデータ。
- 速度: メモリの空き場所を探す処理や、ポインタを経由してアクセスするため、スタックよりは低速。
2. Copy トレイト:スタック上のデータを「コピー」する
Rust では基本的に「代入=所有権の移動(Move)」ですが、特定の型では「コピー」が行われます。これを実現するのがCopyトレイトです。
Copy トレイトを持つ型
-
u32,i32などの数値型 boolchar- これらのみで構成される構造体(タプルなど)
これらはデータそのものがスタック上にあり、サイズも小さい(固定長)ため、代入時にビット単位でコピーしてもコストが無視できるほど小さいです。そのため、Rust はデフォルトで値を複製します。
Copy の挙動(Shallow Copy だが実体ごとコピー)
Copyは第一階層(スタック上のデータ)だけをコピーします。プリミティブ型の場合、第一階層=データそのものなので、完全に独立した値になります。
3. Clone トレイト:ヒープごと「ディープコピー」する
StringやVec<T>などの可変長データは、スタックに「ポインタ・長さ・容量」を持ち、実体はヒープにあります。
これらを単純にビットコピー(Shallow Copy)すると、「2 つの変数が同じヒープメモリを指す」ことになり、メモリの二重解放エラーの原因になります。
そのため、Rust ではこれらの型の代入は「Move(所有権の移動)」となり、元の変数は無効化されます。
どうしても複製したい場合に使うのが、明示的な宣言が必要なCloneトレイトです。
Clone の挙動(Deep Copy)
Cloneは、スタック上のポインタだけでなく、ヒープにあるデータ実体も含めて丸ごと新しい領域にコピーします。
Clone の特徴
-
明示的:
.clone()メソッドを呼ぶ必要がある。 - 高コスト: ヒープ領域の確保とデータのコピーが発生するため重い処理。
- 独立性: コピー後は完全に独立したデータとなり、片方を変更してももう片方には影響しない。
4. まとめ
| 特徴 | Copy (Stack) | Clone (Heap) |
|---|---|---|
| 対象 | プリミティブ型 (u32, char, bool) | 複雑な型 (String, Vec, 構造体) |
| 挙動 | 暗黙的にビットコピー | 明示的 (.clone()) に領域確保してコピー |
| 種別 | Shallow Copy (実体が Stack にあるため完全複製) | Deep Copy (Heap の実体も含めて複製) |
| 速度 | 高速 | 低速(メモリ確保が必要) |
| 所有権 | 移動しない(両方使える) | 移動しない(両方使えるが別物になる) |
Discussion