Cと少し違うところがある感じがしたのでメモ。
前提として、Cでのポインタ、参照などを分かっていないと難しい。
所有権について
rustには所有権という概念がある。
String型など、ヒープによって確保されるデータ(コンパイル時にサイズが不明なデータとも言える)を変数に格納するとき、そのデータ ”そのもの” ではなく ”データのポインタ” が格納される。(Cでの文字列と同じ感じ。)
もし、下コードのように x
がString型の変数であり、そこから y = x
とするとき、x, y
どちらも、同じ"データのポインタ"が格納される。
そして、rustはスコープを抜けたら、メモリを開放するという特徴がある。
もしx, y
で同じポインタを持っているのであれば、同じメモリを解放しようとしてしまう。
これを防ぐためのものが”所有権”である。
y = x
としたときに所有権がyに移る、つまりxは無効にされるのである。
なので下コードはコンパイルが通らない。
fn mov()
{
// 前提 rustはスコープから抜けたらメモリを開放する
let x = String::from("hello");
let y = x;
// この時、”所有権”がyに渡される
// 渡されたらxは無効となる(開放するとき、xが有効だとx, yで同じアドレスを開放することになってしまう)
// 所有権はポインタとは違う(参照ではない)
println!("{}, {}", x, y);
// この場合コンパイルエラーが発生(xを指定したため)
}
ちなみにヒープで確保されないデータ(スタックで確保されるデータ、整数型など。)では、値がコピーされるので、下のコードのように
fn cpy()
{
let x = 5;
let y = x;
println!("{}, {}", x, y);
//出力結果:5, 5
}
とすることができる。
所有権はそのままで、データを参照する
参照を使えば、所有権は移動しないで、値を見ることができる。
fn mov()
{
let x = String::from("hello");
let y = &x;
// yはxを参照する 所有権は移動しない
println!("{}, {}", x, y);
// 出力結果:hello, hello
// この場合コンパイルエラーは発生しない
}
ここでは、x
は文字列データのポインタを持っており、y
はxへのポインタを持っている
つまり、二重開放にならず、エラーを吐かない。
ただし、ここでは参照なので、x
の値が変化したらy
も変化してしまう。
借用されているのでそもそもxの値を変化できないです(&mutとか使えば違うのかもしれないですけど)。2か月前の自分変なこと書いてる...
それが嫌なら、depp copyをすればいい。これでヒープデータが完璧にコピーされる。
fn mov()
{
let x = String::from("hello");
let y = x.clone();
// yはxのdeep copyとなる つまりhelloという文字列がメモリ空間に二つ確保される
println!("{}, {}", x, y);
// 出力結果:hello, hello
}
まとめ
ヒープで確保されるデータ ... 一つのデータで、アドレスを参照することによってやりくり -> ムーブ
rustはこのようなデータはデフォルトでは一つでやりくりする?
スタックで確保されるデータ ... データを複製できる -> コピー
所有権が移る = データがムーブされるといった感じでいいのだろうか。