Rust言語でのデータの代入について
Rustではデータを変数を介して代入する場合の実装に「移動」と「コピー」があります。
この違いを理解しておかないと記述したプログラムが意図通りに動作しないことがあるので注意が必要です。
「移動」と「コピー」
Rustではデータの代入を「移動のセマンティクス(Move Symantics)」、「コピーのセマンティクス(Copy Symantics)」という2つの方式で実装を分けています。実際に移動とコピーがどのように違うのか記載します。
移動のセマンティクス
「移動」はオブジェクト(メモリ上のデータ)を移動するのではなく、そのオブジェクトの所有権を移動する動作になります。
let v = vec![1,100,1000];
let w = v;
上記コードでは2行目の代入で移動が発生します。
この動きを図示すると以下の様になります。
・1行目
・2行目
「移動」と言った場合、直感的にはデータ実体(オブジェクト)がメモリ上で移動する印象を受けますが、実際にはヒープ上のオブジェクトはそのままで、v,wの自動変数の間でオブジェクトの所有権が「移動」します。
もう一つの例は構造体ですが、構造体を自動変数として宣言した場合はオブジェクトはヒープではなくスタック上に確保されます。
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 10, y: 20 };
let q = p;
上記コードでは6行目の代入で移動が発生します。
この動きを図示すると以下の様になります。
・5行目
・6行目
スタック上のオブジェクトはそのままで、p,qの自動変数の間でオブジェクトの所有権が「移動」します。
コピーのセマンティクス
移動の場合、オブジェクトは単一のままですが、「コピー」の場合はオブジェクトのデータ実体を完全に複製します。
移動と同様にコピーもスタック、ヒープの両方のオブジェクトに適用されますが、スタックの場合は「コピー」、ヒープの場合は「クローン」と区別されます。
コピー
let a = 1;
leb b = a;
上記コードでは2行目の代入でスタック上でのコピー(オブジェクトの複製)が発生します。「移動」で例示した構造体のケースと異なり、複製された2つの変数が指すオブジェクトは別物になります。このため所有権の移動は発生しません。
・1行目
・2行目
また、後述しますが、構造体に対してCopyトレイトを実装すると構造体についてもこの例の整数型のプリミティブな型と同様にコピーを複製することが可能になります。
クローン
ヒープ上のオブジェクトに関しては単純に代入するだけでは複製(コピー)を作成することはできません。
この場合はオブジェクトの.clone()メソッドを呼び出す必要があります。
let v = vec![1,100,1000];
let w = v.clone();
・1行目
・2行目
上記コードでは2行目の代入でヒープ上でのコピー(オブジェクトの複製=クローン)が発生します。「移動」で例示したケースと異なり、複製された2つの変数が指すオブジェクトは別物になります。このため所有権の移動は発生しません。
※ オブジェクトは宣言順にスタック上では下位アドレスに、ヒープでは一般的に上位アドレスに配置されます(ただしヒープは実行時のメモリ管理の都合で上位に配置される可能性もあります)。
まとめ
「移動」と「コピー」についてその実装も含めて以下のようなルールになっています。
デフォルト動作
- プリミティブな型は「コピー」
- 数値、論理(bool)、配列、タプル、参照、静的文字列
- 移動
- 動的文字列、Box、コレクション、列挙体、構造体、タプル構造体
またデフォルトが「移動」のものをコピー可能とするためにはclone()メソッドを用意する必要があります(動的文字列、Box、Vectorについては実装済み)
コピーとクローンについての留意点
コピーとクローンについては、それぞれCopyトレイト、Cloneトレイトを実装することで実現可能です。
ただし、Copyのみの実装は不可で、Copyは実装する必要があります。
- その他ルール
- ・プリミティブな型については両方実装されている
- ・コレクションはCloneしか定義できない
- ・ファイルハンドルの様に単一の実物と関連づいたものは両方とも不可
- ・列挙体、構造体、タプル構造体は両方を実装すればプリミティブな型と同様に扱えるようになる