テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(rust report) Rustのポインタ調査

Rustは, オブジェクト(or メモリ領域)へのアクセスコントロールに厳しい言語だ. このアクセスコントロールのために, OwnershipとLifetimeという要素を導入している. Lifetimeとは典型的にはブロックの範囲である. このようにしている理由は, オブジェクトが知らず知らずの上に持ち回されて変更されていないことを静的にチェックするためである. ちなみにこの基本的な考え方はRustがオリジナルではなく, Cycloneという言語から輸入したものであるようだ. また, オブジェクトはデフォルトでImmutableであり, ImmutabilityとMutabilityは明確に分離される. さながらHaskellである.

Rustの狙いは, リアル・ワールドのシステムプログラミングに利用されているC/C++の後継である. 安全なシステムプログラミングのためにはこのような保護が有効らしい. しかし, 私は今のところ, 先ほど「らしい」と言ったように, この「過保護」に懐疑的である. むしろ足かせのように感じていて, Rustの他の機能(Rustは先端的な言語機能をさまざまimportしている)をアテにして勉強しているだけである. きっとこの足かせについてものちのちに理解出来るものであろう. 例えば, 他人のコードを使う場合などは, そのコードが想定外の動作をしないことを静的に保証出来るのであれば心強いと思う.

この過保護のせいで, Rustは, 当然動いてよいコードが動かない. 以下では, 実験を通じてRustのポインタの仕組みを解析する. 私が誤ったことを言ってる場合は, @akiradeveloperに指摘してほしい.

Rustは仕様が日々変更されており, ネットで拾えるサンプルコードは動かないことが多い. このブログに書いたコードもいずれ動かなくなるため, 私の環境のrustcを初めに示す.

akira@Hercules:~/rust-attic$ rustc -v
rustc 0.11.0-pre (2563481 2014-06-20 21:31:22 +0000)
host: x86_64-unknown-linux-gnu

実験

以下のコードは, Cプログラマにとっては当然動くと思うだろうが, 「immutable dereferenceに代入出来ません」という静的エラーが出てコンパイルが出来ない. このコードを少しの修正でコンパイル可能とする方法はない.

fn main(){
        let x = 1;
        let y = &x;
        *y = 2; // cannot assign to immutable dereference of '&'-pointer '*y'
        println!("{}", x);
}

もしこれを強引に動かすのであれば,

fn fun(x: & mut int)
        *x = 2;
}
fn main(){
        let mut x = 1;
        fun(& mut x);
        println!("{}", x);
}

or

fn main()
{
        let mut x = 1;
        {
                let y = & mut x;
                *y = 2;
        }
        println!("{}", x);
}

と書けばよい. これらは等価なコードである.

さて, fun()関数がもし, xを他のtaskに渡すことが出来るとどうだろうか?Rustでは「ポインタの又貸しは禁止」がルールである. これによって, xが最初に生成されたブロック内を抜けたあと安全に破棄されることが保証される.

さらにRustでは, 「Ownershipを持った人しかそのオブジェクトにWrite出来ない」がルールである. これはどういうことだろうか.

以下のコードは, (1)-(3)のうち, (1)と(2)を有効にした場合はコンパイルが通るが, (3)を有効にした場合はコンパイルが通らない. すなわち, このコードをこのままコンパイルすることは出来ない.

fn main()
{
        let mut x = box 5;
        if *x < 10 {
                // let y = x.clone(); // (1) OK
                // let y = *x; // (2) OK
                let y = &x; // (3) cannot assign to *x because it is borrowed
                *x -= 1;
                println!("{}", y);
        }
        *x -= 1;
        println!("{}", x);
}

ここで用語をいくつか紹介しなければならない.

  • Owned Pointer: boxというのは, heapに確保するという意味である. そしてここでxは, Owned Pointerという. これは「ポインタなのだけど, Ownershipを持っているよ」という意味である. このことは, Rustでは, ポインタがOwnershipを持っていることは特別な言葉をつけるに値する事実だということを意味している.
  • Borrowed Pointer: 一方, 初めに示した例では, xはスタック上に確保される. そして&xはご想像どおり, アドレスを表す. RustではこのポインタをBorrowed Pointerと呼ぶ. BorrowedというのはOwnershipを借りているという意味である. (3)はxがyにborrowされているため, *xに対して代入が出来ないということを言っている. 「貸出中のオブジェクトは貸出先が返してくれるまでWriteが禁じられる」がルールである.

この例を変形して, さらに2つのルールについて調査する.

  1. Mutableとして貸し出されたポインタはReadすら出来なくなる(なぜならばあるオブジェクトについてそのOwner以外がReadする場合, その状態が安定していることが保証されてなければならないから)
  2. OwenershipがMoveした場合, 一切のアクセス失う.

以下のケースでは, heap上にある5という値に対するOwned Pointer xを指すポインタyにMutableでBorrowしている. 直感的には, 5がプリントされるものと思うだろうが cannot borrow 'x' as immutable because it is also borrowed as mutable とエラーが出てコンパイル出来ない.

fn main()
{
        let mut x = box 5;
        if *x < 10 {
                let y = &mut x;
                println!("{}", x);
        }

}

実際のコードは知らないが, println!は以下のような実装になっていて, ふつうのborrowをすることを想定してるのだろう.

fn println<T>(o: & T) {
   T::show(o);
} 

以下のケースでは, xをyにMoveしている. MoveとはOwnershipを完全に移すことである. これによって, xはbox 5へのアクセス権を一切失う. このコードをコンパイルしようとすると, use of moved value 'x'と出てコンパイル出来ない.

fn main()
{
        let mut x = box 5;
        if *x < 10 {
                let y = x;
                println!("{}", x);
        }
}

ルールをまとめる

  • Borrowed PointerをさらにBorrowすることは出来ない (又貸し禁止)
  • Owenershipを持った人のみがWrite出来る. LenderはBorrowerがオブジェクトを放すまで, Write出来ない.
  • MutableでlendされているポインタをImmutableでborrowすることは出来ない.
  • Moveは, Ownershipを完全に放棄する. Readすら出来なくなる.

まとめ

実験を通して, Rustのポインタの挙動を調査した. Rustのポインタを理解するためにはOwnership, Lifetime, Mutabilityに関する理解が必要である.

感想: Rustはそもそも, オブジェクトを書き換えていくというモデルを例外として扱っている. メモリ上に副作用を発生させるというコードではなく, 新しいオブジェクトを作っていくというコードの方が良いと思う. 恐らくだが, そのようなコードに対して性能も最適化されているはずである(例えば, C++がやってるRVOとか. http://doc.rust-lang.org/guide-pointers.html see Returning Pointers)

Rustに未来はあるのかと聞かれた. Rustに未来はない. Rustの未来はこれから作るからだ.