RustのOwnershipについて読んでみた
Rustのownershipの考えは特徴的なので、慣れないと今後「あれ??」ってなりそう。 後で振り返られるように軽くまとめておきます。
Ownershipとは
日本語で所有権を意味するこの言葉は、その文字通り、ある値に対する「所有権」を表している。
このOwnershipには、以下の3つのルールがある。
- Each value in Rust has an owner.
- Rustの各値には、所有者がいる
- There can only be one owner at a time.
- 一度になれる所有者は、たった1つ
- When the owner goes out of scope, the value will be dropped.
- 所有者がスコープから外れたら、もれなく値も消える
各ルールを細かく見ていく。
Each value in Rust has an owner.
以下のコードで、ヒープ領域に格納された”hello”の所有者は、s1になる。
let s1 = String::from("hello");
There can only be one owner at a time.
ここがミソで、例えば以下のコードはコンパイルエラーとなる。
なぜならString型の hello
の所有権を、s1とs2の両方が保持することができないからである。
let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // s1にはもう所有権はない
最終的な所有権はs1からs2に移っている。(=move)
When the owner goes out of scope, the value will be dropped.
所有者がスコープから外れれば、もれなく値も消えます。
これはRustが安全と言われる理由の1つだと思います。
{ let s = String::from("hello"); } println!("{}, world!", s); // sはスコープから外れているので、データもろとも破棄されている
Reference
Ownershipは、メモリを安全・効率的に利用するのに非常に有効です。
ですが、以下のように、関数やメソッドを利用する場合、毎度所有権を返さないとなりません。
fn main() { let s1 = String::from("hello"); let (s2, ok) = is_valid(s1); // s1を所有権ごと渡しているので、s1はここで無効になる println!("{} is {}.", s2, ok); } fn is_valid(s: String) -> (String, bool) { let ok = s.len() > 0; (s, ok) // boolだけ返したいのに、Stringの所有権も返さないと、呼び出し側で使えない }
このような場合、毎回所有権を動かすのではなく、ヒープ領域に格納されているデータのアドレスをReference(参照)することで、所有権を動かすことなく、データを利用することができます。
これの仕組みをBorrowと呼びます。
fn main() { let s1 = String::from("hello"); let ok = is_valid(&s1); // s1の所有権は渡さず、参照だけ渡している println!("{} is {}.", s1, ok); } fn is_valid(s: &String) -> bool { s.len() > 0 }
デフォルトだと、ReferenceもImmutableですが、MutableなReferenceをBorrowすることもできます。
これにより、Referenceを通じて値を変更することができます。
let mut s1 = String::from("hello"); let r1 = &mut s1; r1.push_str(", world"); println!("{}", r1);
ただこのReferenceにも、ルールがあります。
- At any given time, you can have either one mutable reference or any number of immutable references.
- いかなる時も、1つのmutableなreference、または任意の数のimmutableなreferenceのどちらかしか持てない
- References must always be valid.
- referenceは常に正しくなければならない。
ここでも各ルールについてみてみましょう。
At any given time, you can have either one mutable reference or any number of immutable references.
つまりReferenceに関しては、以下の2パターンのどちらかしか、許容されていません。
- たった1つのMutableなReference
- 任意の数のImmutableなReference
let mut s1 = String::from("hello"); let r1_1 = &s1; let r1_2 = &s1; // OK println!("{}, {}", r1_1, r1_2); let mut s2 = String::from("hello"); let r2_1 = &s2; let r2_2 = &mut s2; // 同時にmutableとimmutableなreferenceは不可 println!("{}, {}", r2_1, r2_2); let mut s3 = String::from("hello"); let r3_1 = &mut s3; let r3_2 = &mut s3; // 複数のmutableなreferenceは不可 println!("{}, {}", r3_1, r3_2);
MutableなReference先にあるのデータと、同じものを指すReferenceがあるとします。
もしReferenceに変更が生じてしまったら、他のReferenceにも同期する必要があります。
これは非常に悩まされる問題で、下手をすると、もう有効ではない参照先をずっと保持している事態にもなりかねません。(=Dangling References)
Rustではこれを安全に解決するために、上記ルールを設けられています。
References must always be valid.
参照は常に有効でなければなりません。
参照が無効になる場合、コンパイル時に検知されるので、Rustは安全な仕組みになっています。
fn dangle() -> &String { // 最終的に参照が無効になるため、コンパイルエラー let s = String::from("hello"); &s // Stringの参照を返している } // sがスコープから外れ、メモリーが開放されてしまう
最後に
本当にRustはよくできているな、と感心させられました。
絶対にOwnershipは、Rust初心者の私は躓くと思うので、何度でも初心に帰ろうと思います。