Rustの所有権


Rustはひとつのリソースについて所有権が二つ以上あってはいけません。

たとえば可変長な文字列を扱う際にRustはその文字列をヒープ領域に置きます。

let rust_str = String::from("Rust String");


このように文字列を宣言した場合「Rust String」という文字列がヒープ領域に置かれ、
その先頭アドレスがrust_strに格納されるという動きになっているようです。

rust_strは”Rust String”への参照を持っているということですね。
ここで以下のようなソースを書いたとします。

fn main()
{
  let str1 = String::from("Rust String");
  println!("str1 {}", str1);
  let str2 = str1; 
  println!("str2 {}", str2);
  println!("str1 {}", str1);
}


これをコンパイルすると以下のようなエラーが出ました。

error[E0382]: use of moved value: `str1`
 --> str_sample.rs:8:23
  |
6 |   let str2 = str1;
  |       ---- value moved here
7 |   println!("str2 {}", str2);
8 |   println!("str1 {}", str1);
  |                       ^^^^ value used here after move
  |
  = note: move occurs because `str1` has type `std::string::String`, which does not implement the `Copy` trait


これは要するに”Rust String”に対する所有権がstr1からstr2に移動したためstr1からは文字列にアクセスできないということを言っています。
文字列にはコピートレイトが実装されておらず値そのものではなく値に対する参照が渡されます。

この参照が二つ以上あることがRustは気に入らないということでしょう。

このソースをコンパイルしたいのであれば以下のように修正します。

fn main()
{
  let str1 = String::from("Rust String");
  println!("str1 {}", str1);
  let str2 = str1.clone(); 
  println!("str2 {}", str2);
  println!("str1 {}", str1);
}


str1のクローンをstr2に入れてますね。
これによりstr2はstr2のためにヒープ領域に割り当てられたRust Stringを参照するようになりました。

このような現象はコピートレイトが実装されたデータ型には起こりません。
値そのものはコピーされるからですね。

fn main()
{
  let x = 10; 
  let y = x;
  println!("y {}", y);
  println!("x {}", x);
}
y 10
x 10


関数へ渡す時も同じようなことが起こります。
先ほどのソースに関数を追加しました。
そしてstr1を関数へ渡しています。

fn main()
{
  let str1 = String::from("Rust String");
  println!("str1 {}", str1);
  let str2 = str1.clone();

  func(str1);

  println!("str2 {}", str2);
  println!("str1 {}", str1);
}

fn func(str3: String) 
{
  println!("str3 {}", str3);
}


コンパイル時にエラーが出力されます。

error[E0382]: use of moved value: `str1`
  --> str_sample.rs:11:23
   |
8  |   func(str1);
   |        ---- value moved here
...
11 |   println!("str1 {}", str1);
   |                       ^^^^ value used here after move
   |
   = note: move occurs because `str1` has type `std::string::String`, which does not implement the `Copy` trait


理由は先程と同じでfunc関数の引数str3に参照が渡ったため、
str1からはもうアクセスできないということです。
こういう場合は借用というものを使います。

fn main()
{
  let str1 = String::from("Rust String");
  println!("str1 {}", str1);
  let str2 = str1.clone();

  func(&str1);

  println!("str2 {}", str2);
  println!("str1 {}", str1);
}

fn func(str3: &String) 
{
  println!("str3 {}", str3);
}


こうすることでstr3は所有権を一時的に借りる形になります。
スコープから外れたあとは所有権がstr1に戻ります。

今回文字列について言及しましたが、
所有権の厄介ぷりは「参照」というものを利用するリーソス全てに当てはまるかことかと思います。

ここからまたリソースがイミュータブルかミュータブルかで動きがまた変わってきます。
疲れたので今日はこの辺にしておきます。