前回に引き続きRustの所有権システムについての連載をやっていきます. 前回は前提として,rustにおける値と変数の区別について説明しました.
今回は所有権システムの3つのルールについて説明します.
所有権システムとは
所有権システムは,Rustがメモリ管理を行う際のルールの集まりで,ざっくり言うと全体として「プログラム中のある部分において,メモリ中のある値を束縛することのできる変数は一つだけである」ということを言っています.
より正確に言うと,下の3つのルールから成ります*1.
- 値は所有者(owner)と呼ばれる変数を持つ
- 所有者は同時に1つのみ存在する
- 所有者がスコープから外れると値はdropする
3つ目のdropという概念はまだ説明していませんが,これについては次回の記事で説明する予定です. とりあえず今回の記事の範囲では「所有者の変数がスコープから外れると値が無効になって変数を使えなくなる」という理解をしてくれれば十分です.
例1
言葉だけで説明されてもあまり腑に落ちないと思うので,コード例で説明していきます.
fn main() { // Box::new()によって値を格納するメモリ領域を動的に確保する let x = Box::new(100); // ここでは変数xは使える println!("{}", x); let y = x; // アンコメントアウトするとエラー // println!("{}", x); println!("{}", y); }
このコードでは変数xに,メモリ中に動的に確保した100という値*2を束縛させています. その後その値を再びyという別の変数に束縛させています.
ここでコード中でコメントアウトされている文をアンコメントアウトすると*3,下のようなエラーが起こります.
error[E0382]: borrow of moved value: `x` --> src/main.rs:10:20 | 3 | let x = Box::new(100); | - move occurs because `x` has type `Box<i32>`, which does not implement the `Copy` trait ... 7 | let y = x; | - value moved here ... 10 | println!("{}", x); | ^ value borrowed here after move | = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0382`.
何やらいろいろ言われていますが重要なのは,let y = x;
という文で,xが束縛している値の所有権が変数yに移動しているということです.
これが1つ目と2つ目のルールが表れている部分で,rustに初めて触れた人は驚くところでしょう.
これらのルールによって,100という値には同時にたった一つの所有者しか存在することができず,結果的に値をyに束縛しようとするとxの束縛は外すしかありません.
3つ目のルールについては次回以降でDropについて説明するときにまた説明します.
例2
先程の例ではただの値ではなくわざわざBoxを使っていたことに気づいた方がいるかもしれません. そこで普通の値を使って先程の例のコードを書き直してみます.
fn main() { let x = 100; let y = x; // コメントなしだとエラー...? println!("{}",x); println!("{}",y); }
所有権のルールからすると,このコードは先程の例と同じエラーが出てコンパイルできないはずです. しかし,このコードはコンパイルが通ります.
所有権のルールと照らし合わせてみると,値100の所有権はyに移っているはずなのでxをその後使うことはできないはずです. さっきの例と何が違うのでしょうか?
この違いについては次回に説明していこうと思います.
*1:https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#ownership-rules
*2:正確にはヒープ中に格納された値100を指すBox型の値です