Edited at

Rustの所有権に親しむ

More than 1 year has passed since last update.

遅れてしまいましたが、これは Rust その2 Advent Calendar 2016 の 5日目の記事です。

この記事はRustをあまり知らない人でも理解できると思います。

Rustを学んで所有権の仕組みに感動したので、

自分の理解の確認を兼ねて所有権のポイントをまとめました。

また、所有権によって得られるメリットを二つ、所有権の魅力として書いて見ました。


所有権の原則

RustはGCなし(ゼロコスト)でリソースを自動的に解放する仕組みを持っていて、それを実現する概念が所有権です。

Rustでは変数(束縛)がスコープを抜けるとき、リソースが解放されます。

スコープを抜けた時に全てのリソースが解放されなくてはいけないので、

変数に対する参照がある場合も、参照は参照元のスコープより長く存続できません。


3つのポイントで所有権を理解する

上記の原則を前提として、実際にプログラミングするときには

次の3つを意識すれば「借用チェッカとの争い」をしなくて済みそうです。


  1. 変数を束縛(代入)すると元の変数はアクセスできなくなる

  2. 参照は幾つでも作れる

  3. mutableは一つだけ


1.変数を束縛(代入)すると元の変数はアクセスできなくなる

let v1 = vec![1, 2, 3];

let v2 = v1;
println!("Hello, {}!", v1[1]); // error[E0382]: use of moved value: `v1`


2.参照は幾つでも作れる

let v1 = vec![1, 2, 3];

let v2 = &v1; // 代入時に&をつけると参照になります
let v3 = &v2;
println!("Hello, {}, {}, {} !", v1[0], v2[1], v3[2]); // no error


3.mutableは一つだけ

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


3'既に参照のある変数からはmutable参照できない

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


3''順番を入れ替えてもダメ(参照を既に持っていると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


3'''mutable参照から通常の参照は作れるが、mutable参照に書き込みできなくなる

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


3''''もちろんスコープを抜ければ書き込める

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


これは所有権とメソッド構文の合わせ技で実現しています。


Vec.push()の実装

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の所有権システム良いですね。