遅れてしまいましたが、これは Rust その2 Advent Calendar 2016 の 5日目の記事です。
この記事はRustをあまり知らない人でも理解できると思います。
Rustを学んで所有権の仕組みに感動したので、
自分の理解の確認を兼ねて所有権のポイントをまとめました。
また、所有権によって得られるメリットを二つ、所有権の魅力として書いて見ました。
所有権の原則
RustはGCなし(ゼロコスト)でリソースを自動的に解放する仕組みを持っていて、それを実現する概念が所有権です。
Rustでは変数(束縛)がスコープを抜けるとき、リソースが解放されます。
スコープを抜けた時に全てのリソースが解放されなくてはいけないので、
変数に対する参照がある場合も、参照は参照元のスコープより長く存続できません。
3つのポイントで所有権を理解する
上記の原則を前提として、実際にプログラミングするときには
次の3つを意識すれば「借用チェッカとの争い」をしなくて済みそうです。
- 変数を束縛(代入)すると元の変数はアクセスできなくなる
- 参照は幾つでも作れる
- mutableは一つだけ
let v1 = vec![1, 2, 3];
let v2 = v1;
println!("Hello, {}!", v1[1]); // error[E0382]: use of moved value: `v1`
let v1 = vec![1, 2, 3];
let v2 = &v1; // 代入時に&をつけると参照になります
let v3 = &v2;
println!("Hello, {}, {}, {} !", v1[0], v2[1], v3[2]); // no error
let mut v1 = vec![1, 2, 3];
let mut v2 = &mut v1; // &mutをつけるとmutable参照になり書き込みできます。
let mut v3 = &mut v1; // error[E0499]: cannot borrow `v1` as mutable more than once at a time
let mut v1 = vec![1, 2, 3];
let mut v2 = &mut v1; //no error
let v3 = &v1; //error[E0502]: cannot borrow `v1` as immutable because it is also borrowed as mutable
let mut v1 = vec![1, 2, 3];
let v3 = &v1; //no error
let mut v2 = &mut v1; //error[E0502]: cannot borrow `v1` as mutable because it is also borrowed as immutable
let mut v1 = vec![1, 2, 3];
let mut v2 = &mut v1;
let v3 = &v2; //no error
*v2 = vec![1, 5, 3]; //error[E0506]: cannot assign to `*v2` because it is borrowed
let mut v1 = vec![1, 2, 3];
let mut v2 = &mut v1;
{
let v3 = &v2; //no error
}
*v2 = vec![1, 5, 3]; //no error
所有権の魅力
変数のスコープを小さくする作用がある
束縛のデフォルトの動作は、束縛された変数に以後アクセスできなくなるという、ユニークで面倒くさいルールですが、
このルールは変数のスコープやメソッドの長さを小さく保つようにプログラマーに作用し、コードを綺麗にする効果があると思います。
不変と可変の使い分けがとても楽
mutの有無で変数自体への書き込みを制御するだけではなく、
変数にセットされたインスタンス変数(structのデータ)への書き込みも同時に制御できます。
let mut mutable_vector: Vec<i32> = (0..10).collect();
mutable_vector.push(0); // no error
let imutable_vector: Vec<i32> = (0..10).collect();
imutable_vector.push(0); // error: cannot borrow immutable local variable `imutable_vector` as mutable
これは所有権とメソッド構文の合わせ技で実現しています。
pub fn push(&mut self, value: T) {
// This will panic or abort if we would allocate > isize::MAX bytes
// or if the length increment would overflow for zero-sized types.
if self.len == self.buf.cap() {
self.buf.double();
}
unsafe {
let end = self.as_mut_ptr().offset(self.len as isize);
ptr::write(end, value);
self.len += 1;
}
}
Rustはメソッドからインスタンス変数(structのデータ)への直接参照がありません。
自身のメンバ変数へアクセスする関数は第一引数で参照($selfや&mut self)を受け取るように定義します。
ただし関数を呼び出す時は第一引数を省略して「変数名.関数名(第二引数)」と書きます。
こう考えると、mutを外すとpushできなくなることも、「cannot borrow immutable local variable」と言われることも納得できます。
また、不変オブジェクトの全ての内部データが不変であることも確実に保証されることにもなります。
JavaやScalaでは不変と可変のオブジェクト変換は手間もコストもかかるので、この仕組みには感動しました。
おわり
Rustの所有権システム良いですね。