所有権

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は参照できる
}

参考