はじめに
Rustの所有権について勉強したことをまとめました。
所有権とは
所有権とはRustにおける独特なメモリ管理方法です。
従来のプログラミング言語では以下の様な方法でメモリを管理していました。
-
プログラマの責任で明示的に領域の確保と開放を行う
-
ガベージコレクションで不要な領域を自動的に開放する
Rustでは上記2つとは違う方法でメモリを管理しています。
所有権の規則
所有権には以下の3つの規則があります。
-
Rustの各値は、所有者と呼ばれる変数と対応している
-
いかなる時も所有者は一つである
-
所有者がスコープから外れたら、値は破棄される
所有者
所有者とはデータを所有している変数のことです。
以下の場合、変数sは文字列Helloの所有者になります。
fn main() {
let s = String::from("Hello");
println!("{}", s);
}
main関数の実行が完了すると変数sのスコープから外れるので
文字列Helloのメモリ領域は自動で開放されます。
所有権の移動
上記の規則にあるように、所有者は常に一つだけです。
そのため、変数の値を別の変数に代入すると新しい変数が所有者になります。
これを所有権の移動と言います。
下記の例では所有権が変数xから変数yに移動しています。
所有権が移動した場合、元の所有者である変数は使用できなくなります。
let x = String::from("Hello");
let y = x;
下記のコードはコンパイルエラーになります。
所有権が移動した後に変数xの値を出力しているためです。
fn main() {
let x = String::from("Hello");
let y = x; // 所有権が変数yに移動し変数xは使えなくなる
println!("{}", x);
}
所有権の移動は関数の引数でも発生します。
引数に値渡しするとその引数が新しい所有者になります。
下記のコードもコンパイルエラーになります。
fn print_string(s: String) {
println!("{}", s);
}
fn main() {
let x = String::from("Hello");
print_string(x); // print_string関数の引数sが所有者になる
println!("{}", x);
}
引数を値ではなく参照にすると所有権は移動しません。
型名に&を付けると参照型になります。
呼び出し側は変数の前に&付けます。
fn print_string(s: &String) {
println!("{}", s);
}
fn main() {
let x = String::from("Hello");
print_string(&x); // 所有権は移動しない
println!("{}", x);
}
メモリ
所有権が移動する際のメモリは以下の様になります。
-
スタック上の値はコピーされる
-
ヒープの領域は変わらない
下記の様に所有権が移動した場合、メモリの内容は図の通りです。
let x = String::from("Hello");
let y = x;
プリミティブ型
プリミティブ型(整数型、小数点型、bool型等)は所有権の移動は発生しません。
これは値のコピーが自動的に行われるためです。
そのため以下のコードはコンパイルエラーになりません。
fn main() {
let x = 1;
let y = x; // 変数xの値が変数yにコピーされる
println!("{} {}", x, y);
}
スコープ
波括弧({})を使って変数のスコープを制限することができます。
スコープを抜けると、そのスコープで宣言した変数は使用できなくなります。
fn main() {
let x = 1;
{
let y = 2; // ネストされたスコープでのみ有効な変数y
println!("{}", y);
}
println!("{} {}", x, y); // 変数yはスコープ外のためコンパイルエラーになる
}
まとめ
-
所有権とはRustにおけるメモリの管理方法
-
変数に値を代入するとその変数が所有者となる
-
変数の値を別の変数に代入すると新しい変数が所有者となる(所有権の移動)
元の所有者の変数は使用できなくなる -
プリミティブ型は値がコピーされるため所有権は移動しない
-
スコープを抜けるとそのスコープ内で宣言した変数は使用できなくなり値は開放される
参考文献

