よく分からないので調べたことをまとめた。スタック・ヒープに関しては正確に捉えられているかどうか怪しい。
* 20180825 @scivola さん、@uasi さんから指摘をいただき、修正しました。
所有権
rust の変数束縛は束縛されている値の「所有権を持つ」。
これは「束縛がスコープから外れるとき、Rust は束縛されているリソースを解放する」ということ、この挙動は他の言語とあまり変わりない。
fn foo() {
let v = vec![1, 2, 3];
// ベクタがスタックに作成され、要素を格納するためにヒープに空間を割り当てられる(スタックアロケート)
} // <- スコープから外れる時、ベクタに関連するものがスタックから削除される(スタックデアロケート)
ムーブセマンティクス
Rust は与えられたリソースに対する束縛が1つだけで
あることを保証する。
別の変数に束縛できるが、元々の変数が参照できなくなる。つまり、所有権が移動する。
let v = vec![1, 2, 3];
let v2 = v;// <- value moved here
println!("v[0] is: {}", v[0]);// <- Error!!! value used here after move
下記の例では 所有権がmain関数スコープのv
からtake関数のスコープのv
に移動するため、 同様にエラーが出力される。
fn main() {
let v = vec![1, 2, 3];
take(v);// <- value moved here
println!("v[0] is: {}", v[0]);// <- Error!!! value used here after move
}
fn take(v: Vec<i32>) {
println!("v[0] is: {}", v[0]);
}
なぜ?
以下のコード例で説明する。
let v = vec![1, 2, 3];
let mut _v2 = v;// <-- value moved here
println!("v[0] is {}", v[0]);// <-- Error!!! value used here after move
1行目ではベクタオブジェクトvのために、スタック上にメモリを割り当てる。そして、先頭の要素v[0]
のヒープ領域のアドレス(2^30)-3
をポインタとして持つ。
Address | Name | Value | Note |
---|---|---|---|
(2^30)-1 | 3 | v[2]の値 | |
(2^30)-2 | 2 | v[1]の値 | |
(2^30)-3 | 1 | v[0]の値 | |
... | ... | ... | ... |
3 | v.len | 3 | vのサイズ |
2 | v.buf.allocator | some allocator | 可変長のためバッファを持つ |
1 | v.buf.capacity | 3 | vの容量 |
0 | v.buf.ptr | ->(2^30)-3 | 先頭要素のヒープ領域のポインタ |
2行目では所有権が移動する。つまり、ベクタオブジェクトvの持つスタック領域の情報をベクタオブジェクト_v2にコピーする。よって同じヒープ領域を示すポインタが2つ存在することになる。
Address | Name | Value |
---|---|---|
(2^30)-1 | 3 | |
(2^30)-2 | 2 | |
(2^30)-3 | 1 | |
... | ... | ... |
~~ | ~~ | ~~ |
4 | _v2[0] | ->(2^30)-3 |
~~ | ~~ | ~~ |
0 | v[0] | ->(2^30)-3 |
例えば,_v2.truncate(2)
といった処理を行い、要素を2つ切り詰めたとする。そうなると、ベクタオブジェクトvは存在しないヒープ領域をポインタに持つことになる。このようなデータ競合を防ぐため、Rustではコピー元のベクタオブジェクトvの使用を禁止する
。
プリミティブ型は違う
所有権が移動した時、元の変数は使用できない。しかし、i32
などのプリミティブ型はその限りでない。これはCopy トレイト
を実装しており、所有権の移動ではなく、完全なコピーを行なっているためである。
なぜプリミティブ型は「完全なコピー」なのか
以下のコード例で説明する。
let x: i32 = 1;
let _y: i32 = x;
println!("x is {}", x); // x is 1
1行目のスタックアロケートは以下のようになる。
Address | Name | Value |
---|---|---|
... | ... | ... |
0 | x | 1 |
2行目では単純にxの値を参照して、_yへの代入が行われるため、以下のようになる。
Address | Name | Value |
---|---|---|
... | ... | ... |
1 | _y | 1 |
0 | x | 1 |
プリミティブ型はベクタオブジェクトとは異なり、ヒープ領域を利用しない。つまり、別のアドレスへのポインタを持たない。よって、データ競合は起こりえないため、完全なコピーが許されている。