5
2

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 の参照と借用

Posted at

よく分からないので調べたことをまとめた。

所有権の厄介さ

i32 型のベクタオブジェクト v1、ベクタオブジェクト v2 の要素の総和を返す関数を例とする。

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5, 6];
    let sum = sum_vector(v1, v2);
    println!("v1 and v2 sum is {}", sum);// <-- value moved here
    println!("v1 is {:?} ", v1); // <-- Error!!! value used here after move
    println!("v2 is {:?} ", v2); // <-- Error!!! value used here after move

}

fn sum_vector(v1: Vec<i32>, v2: Vec<i32>) -> i32 {
    let v1_sum = v1.iter().fold(0, |sum, x| sum + x);
    let v2_sum = v2.iter().fold(0, |sum, x| sum + x);
    v1_sum + v2_sum
}

ベクタオブジェクト v1 はsum_vector関数のスコープ内に所有権が移動しているため、sum_vector関数のスコープから外れた後に、呼び出し元のmain関数内で参照するとエラーが出力される。v2 も同様である。

再度main関数内で使用するなら、sum_vector関数の戻り値にベクタオブジェクト v1 及び v2 を含める必要がある。

以下の例ではタプルに含めてで返すようにsum_vector関数を変更した。

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5, 6];
    let (v1, v2, sum) = sum_vector(v1, v2);
    println!("sum is {}", sum); // v1 and v2 sum is 21
    println!("v1 is {:?} ", v1); // v1 is [1, 2, 3]
    println!("v2 is {:?} ", v2); // v2 is [4, 5, 6]
}

fn sum_vector(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    let v1_sum = v1.iter().fold(0, |sum, x| sum + x);
    let v2_sum = v2.iter().fold(0, |sum, x| sum + x);
    (v1, v2, v1_sum + v2_sum)
}

これは、嫌だ。

借用

以下のコードに書き換えることで、所有権の借用を行うことができる。

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5, 6];
    let sum = sum_vector(&v1, &v2);
    println!("sum is {}", sum);
    println!("v1 is {:?} ", v1);
    println!("v2 is {:?} ", v2);
}

fn sum_vector(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    let v1_sum = v1.iter().fold(0, |sum, x| sum + x);
    let v2_sum = v2.iter().fold(0, |sum, x| sum + x);
    v1_sum + v2_sum
}
  1. sum_vector 関数に渡す変数を v1, v2 から &v1, &v2に変更。宣言自体はv1, v2のまま。
  2. 引数の型をVec<i32> -> &Vec<i32>

&T 型は参照と呼ばれ、リソースの所有権を借用する。

参照の種類

通常の参照

&T 型はイミュータブルなので変更できない。下記の例だとコンパイルエラーになる。

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5, 6];
    let sum = sum_vector(&v1, &v2);
    ...
}
fn sum_vector(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    v1.push(5); // cannot borrow as mutable
    ...
}

&mut 参照

&mut T 型だと参照しているリソースを変更できる。これを前者を参照と呼ぶのと区別し、&mut参照と呼ぶ

fn main() {
    let mut v1 = vec![1, 2, 3];
    let mut v2 = vec![4, 5, 6];
    let sum = sum_vector(&mut v1, &mut v2);
    ...
}

fn sum_vector(v1: &mut Vec<i32>, v2: &mut Vec<i32>) -> i32 {
    v1.push(5);
    ...
}

&mut 参照は同じスコープでは行えない。下記の例ではコンパイルエラーになる。

fn same_scope(){
    let mut v1 = vec![1, 2, 3];
    let y1 = &mut v1; // v1の&mut参照開始
    y1.push(6);
    println!("v1 is {:?}", v1); // ここでv1を借用しようとするため、エラーになる
    // => cannot borrow `v1` as immutable because it is also borrowed as mutable
}

不正な参照

以下の例だと、スコープの終わりで変数 x が解放されるため、参照が不正となり、コンパイルエラーになる。

    let y: &Vec<i32>;
    {
        let x = vec![1, 2, 3];
        y = &x; // error[E0597]: `x` does not live long enough
    }
    println!("y is {:?}", y);

同じスコープでも、参照する変数が先に宣言されているとコンパイルエラーとなる。
これは後に宣言した変数から順に解放されるためである。下記の例だと変数 x -> y の順に解放されるので、y の参照が不正になる。

    let y: &Vec<i32>;
    let x = vec![1, 2, 3];
    y = &x; // error[E0597]: `x` does not live long enough
    println!("y is {:?}", y);

つまりこれなら大丈夫。

    let x = vec![1, 2, 3];
    let y: &Vec<i32>;
    y = &x;
    println!("y is {:?}", y);// => y is [1, 2, 3]
5
2
0

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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?