8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rustの所有権

Last updated at Posted at 2018-08-24

よく分からないので調べたことをまとめた。スタック・ヒープに関しては正確に捉えられているかどうか怪しい。

* 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

プリミティブ型はベクタオブジェクトとは異なり、ヒープ領域を利用しない。つまり、別のアドレスへのポインタを持たない。よって、データ競合は起こりえないため、完全なコピーが許されている。

8
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?