所有権
Rustのメインのシステムの1つ。 所有権のシステムによってRustはメモリ安全性を実現している
Rustの所有権には以下の規則がある
- 値には所有者が存在する
- いかなる時も所有者は1つのみ
- 所有者がスコープから外れたら値は破棄される
スコープ
変数はスコープの中でのみ有効
println!(x); // error: xはスコープ外なので参照できない
{
let x = String::from("hello");
println!(x); // OK: xはスコープ内なので参照できる
}
println!(x); // error: xはスコープ外なので参照できない
スコープを抜けるとStringの値が破棄される。
破棄されるということは、メモリが解放されるということ。
Rustはこの仕組みを自動的に行ってくれるので、C言語のように手動でメモリを解放する必要がない。
→ メモリ安全性が担保される。
ムーブ
Stringはヒープ領域に値を保持している。
そのため、別の変数に値を移動させると所有権が移動する。
let x = String::from("hello");
let y = x; // ムーブが発生する(xからは参照できなくなる)
println!(x); // compile error: xは参照できない
Stringの場合はcloneメソッドを使うことで、値をコピーすることができる。
let x = String::from("hello");
let y = x.clone(); // cloneを使うことで値をコピーできる
println!(x); // OK: xは参照できる
Copyトレイト
別の変数に代入したときに、所有権をムーブさせない仕組み。
スタックに保存される系の値(数値やboolなど)にはCopyトレイトが実装されており、所有権がムーブされないようになっている。
必要なら自分でCopyトレイトを実装することもできる。
let x = 5;
let y = x; // Copyトレイトが実装されているので、所有権はムーブされない
println!(x); // OK: xは参照できる
参照と借用
通常、別の変数に代入すると所有権が移動する。
しかし、参照だけを渡すことで、所有権を移動させずに値を使うことができる。
参照は&を使って作ることができる。
参照を作成することを借用と呼ぶ。
let x = String::from("hello");
let y = &x; // 参照を渡すことで、所有権を移動させずに値を使うことができる
println!(x); // OK: xは参照できる
println!(y); // OK: yは参照できる
参照は変数の所有権を移動させないので、参照を作った変数はスコープを抜けても破棄されない。
let x = String::from("hello");
{
let y = &x; // 参照を作る
println!(y); // OK: yは参照できる
} // yはスコープを抜けるが、xは破棄されないので、参照は有効
println!(x); // OK: xは参照できる
println!(y); // compile error: yはスコープ外なので参照できない
可変参照
参照は不変参照と可変参照の2種類がある。
先ほどの例のlet y = &x;は不変参照である。
可変変数の参照 + 変更できる権利を持つ = 可変参照
let mut x = String::from("hello");
let y = &mut x; // 可変参照を作る
y.push_str(" world"); // 可変参照を使って値を変更する
println!(y); // OK: yは参照できる
可変参照は同時に複数作ることはできない。 可変参照があるときにはその変数の不変参照も作れない
let mut x = String::from("hello");
let y = &mut x; // 可変参照を作る
let z = &mut x; // compile error: 可変参照は同時に複数作ることはできない
let w = &x; // compile error: 可変参照があるときにはその変数の不変参照も作れない
可変参照がデータの書き込みを行える間、元の変数は参照できない。 (可変参照はデータを同期しているわけではないため)
let mut x = String::from("hello");
let y = &mut x; // 可変参照を作る
println!(x); // compile error: 可変参照があるときにはその変数の不変参照も作れない
(可変参照がスコープを抜けると、元の変数は参照できるようになる)
let mut x = String::from("hello");
{
let y = &mut x; // 可変参照を作る
y.push_str(" world"); // 可変参照を使って値を変更する
println!(y); // OK: yは参照できる: hello world
} // yはスコープを抜けるので、xは参照できる
println!(x); // OK: xは参照できる
NIL(Non-Lexical Lifetimes)
コンパイラが所有権のルールをチェックする際に、どこまでその値が有効かを判断する仕組み
let mut y = String::from(x);
let a = &mut y;
let b = &mut y;
println!("a: {a}, b: {b}");
↑これはコンパイルエラーだけど
↓これはコンパイルエラーじゃない
let x = "hello";
let mut y = String::from(x);
let a = &mut y;
let b = &mut y;
println!("b: {b}");
これ以上その参照が使用されなくなったら、借用が終了する
関数と所有権の組み合わせ
関数の引数に代入するとき、戻り値を変数に代入するときにも所有権のルールが適用される。
fn main() {
let x = String::from("hello");
let doubled_x = double_string(x); // double_stringに所有権が移動する
println!(x); // compile error: xは参照できない
println!(doubled_x); // OK: doubled_xは参照できる
}
fn double_string(s: String) -> String {
s + &s // sの所有権は関数内で破棄されるが、戻り値として返すことで所有権を呼び出し元に移動させる
}
参照を引数にすることで、所有権を移動させずに値を使うことができる。
fn main() {
let x = String::from("hello");
let doubled_x = double_string(&x); // double_stringに所有権が移動しない
println!(x); // OK: xは参照できる
println!(doubled_x); // OK: doubled_xは参照できる
}
fn double_string(s: &String) -> String {
s + &s // sの所有権は関数内で破棄されないので、呼び出し元のxは参照できる
}
可変参照を引数にすることで、所有権を移動させずに値を変更することができる。
fn main() {
let mut x = String::from("hello");
double_string(&mut x); // double_stringに所有権が移動しない
println!(x); // OK: xは参照できる
}
fn double_string(s: &mut String) {
s.push_str(" world"); // sの所有権は関数内で破棄されないので、呼び出し元のxは参照できる
}