Rustの参照
参照を用いると値の所有権に影響を与えずに値にアクセスできる
Rustの参照は絶対にnullにならない
即ちヌルポにならない
一方で、参照は、参照先よりも長生きしてはいけない
普通に考えるとそりゃそうmrsekut.icon
参照しているのに、参照先が先に死んだら、ダングリングポインタになってしまうからなmrsekut.icon 参照には2種類ある
共有参照
shared reference
immutable, read only
書き込むことはできないが、複数箇所から同時に参照ことができる
可変参照
mutable reference
mutable
読み込みも書き込みもできる
誰か一人が可変参照している間は他のやつは参照できない
可変参照はもちろんだが、共有参照もできない
値に対してアンパサンドを用いた時
code:rs
let x = 5i8;
let y = &x; // yはxへの参照、ポインタ、アドレス
assert_ex!(5i8, *y); // ポインタから値を見るには*
yは、値xへの参照である
つまりyは、アドレスであり値(?)ではない
yの参照している値(ここではx)を見るためには、*yとする
ただ、println!("{}", y);を実行した時は、
本来ならアドレスを出力をするはずだが、
Rustがよしなにやって、*yの値を返す、みたいなのがあった気がする
参照の代入
アドレスを指している変数に別の参照を代入すると、参照の向き先が変わる
アドレスを入れていた変数にはアドレスしか入れられない
参照の参照、参照の比較ができる
参考
関数を利用する側からすれば型に合わせればいいだけなので、良いとして問題は関数を定義する際の視点
関数を定義する際に、引数を参照にするかしないかの選択をする
特にarrayなどを受け取ることを考えるとイメージしやすい?
そもそもmutは減らしたいので、考えないものとする
これで4パターンが2パターンに減った
(参照の有無, mutの有無→参照の有無)
方針としては
参照を受け取る
ただ、所有権を移動したいときだけ、参照ではなく実体を受け取る
みたいになりそう
GPT-4.icon
例1: 参照を受け取る関数の例
まず、関数が「参照」を受け取る場合の例です。
code:rs
// Vec<i32>のベクターに要素を追加する関数
fn append_vector(v1: &mut Vec<i32>, v2: &Vec<i32>) {
for &item in v2 {
v1.push(item); // v2の要素をv1に追加
}
}
fn main() {
// 参照を渡して関数を呼び出す
append_vector(&mut v1, &v2);
}
解説
fn append_vector(v1: &mut Vec<i32>, v2: &Vec<i32>): この関数では、v1は&mut Vec<i32>(ミュータブルな参照)で、v2は&Vec<i32>(不変な参照)を受け取っています。
v1はミュータブル参照なので、この関数内で値を変更できます。
v2は不変な参照なので、関数内ではv2を変更できませんが、その要素を使ってv1に追加しています。
append_vector(&mut v1, &v2): v1とv2は実際の値ではなく、参照(&mut v1と&v2)を渡しているため、メモリのコピーは発生せず、関数内でデータを効率的に操作しています。
例2: 所有権を受け取る関数の例
次に、所有権を受け取る場合の例です。
code:rs
// Vec<i32>のベクターに要素を追加する関数(所有権を受け取る)
fn append_vector_ownership(mut v1: Vec<i32>, v2: Vec<i32>) -> Vec<i32> {
for item in v2 {
v1.push(item); // v2の要素をv1に追加
}
v1 // v1を返す
}
fn main() {
// 所有権を渡して関数を呼び出す
let v1 = append_vector_ownership(v1, v2);
}
解説
fn append_vector_ownership(mut v1: Vec<i32>, v2: Vec<i32>) -> Vec<i32>: この関数では、v1とv2は所有権を受け取る形で引数に渡されます。
v1とv2のデータ自体を関数に渡しているため、関数内でv2の内容は消費されます。
関数が終わるとv1の所有権は呼び出し元に返されます(v1を返り値として返す必要があります)。
append_vector_ownership(v1, v2): この場合、v2の所有権が関数に移るため、関数の呼び出し後はv2を再利用することができません。もしv2を再利用しようとすると、コンパイルエラーが発生します。
code:rs
fn main() {
let v1 = append_vector_ownership(v1, v2);
println!("{:?}", v2); // ここでエラー!v2の所有権はすでに関数に渡されている
}
Rustでは所有権を移動させることもできますが、基本的には**参照を使う**ケースが多いです。
基本的に参照を使う理由
1. 効率性: 参照を使うことで、データをコピーせずにアクセスできるため、大きなデータ構造を扱う場合でもメモリ効率が良いです。これにより、所有権を移動せずに関数内でデータを使うことができます。
2. 柔軟性: 参照を使うと、呼び出し元のデータをそのまま維持しながら関数内で処理ができ、関数終了後にもデータを再利用できます。所有権を渡すと、データが使えなくなる(消費される)ため、再利用が難しくなります。
所有権を渡す必要があるケース
所有権を移動させる場合の典型的なシナリオには、次のようなものがあります。
データの完全な移動が必要な場合:
メモリやリソース管理の観点で、あるデータの処理を一箇所に集約したい場合や、ある値を関数に「消費させる」必要がある場合です。例えば、大きなデータ構造やファイルハンドルなど、使い終わったらそのまま手放しても問題ないデータの処理には有効です。
データの内容を変更して新しい値を作る場合:
完全に新しい値を返したいときや、再利用する必要がない場面では所有権を渡して処理することがあります。この場合は、呼び出し元のデータが不要であることが前提です。
結論
通常は参照を使う方が一般的で、Rustでも多くのライブラリや関数は参照を取る形で定義されています。これは、効率的で柔軟な設計が可能だからです。
所有権を渡すのは特別なケースであり、データの消費や大きなデータの所有権を一箇所に集約する場合に使われることが多いです。