mox692 のブログ

妄想の書き留め場所.

RustのOption::take()

too many linked list っていうRustで書きづらいようなデータ構造を頑張って書いて行こうぜみたいなblog postを最近読んでいた.

その中ででてきて面白かったのが Option::take() という関数.

/// Takes the value out of the option, leaving a [`None`] in its place.
///
/// # Examples
///
/// ```
/// let mut x = Some(2);
/// let y = x.take();
/// assert_eq!(x, None);
/// assert_eq!(y, Some(2));
///
/// let mut x: Option<u32> = None;
/// let y = x.take();
/// assert_eq!(x, None);
/// assert_eq!(y, None);
/// ```

Optionに実装されているものはこんな感じで、Optionの中身がSomeだった場合は中のvalueを取り出して返し、元のOption型の変数にはDefault値 (OptoinではNone) を詰める. OptionがNoneだった際はそのままNoneが返り、元のOption変数もNoneのまま.

なぜ面白いと感じたかというと、このtake()を使えば、下記のようなコードを書いてもborrow checkerに怒られることがないからだ. (前提となる全てのコードは載せてません. 詳しくはblog postを覗いてみてください)

pub fn push(&mut self, elem: T) {
    let newlink = Link::Some(Box::new(Node {
        elem: elem,
        next: self.head.take(),
    }));
    self.head = newlink;
}

もしtakeがよくあるようなselfの所有権を奪ってしまう関数だったら、ここでnew_linkにselfの所有権が移ってしまうことでエラーが発生してしまうと思う. しかしOption::take()ではエラーにはならない.

ではなぜエラーにはならないのだろう? これはRustが低レベルでunsafeな操作をいい感じにwrapしてくれていて、その結果take()が可変参照のメソッドとして提供できているからだと考えている.*1 つまり使用者はtake()によってオブジェクトが変更されうるという仕様さえ把握して、それを意識したコーディングをしていれば安全性は担保される.

Rustの静的チェックは基本的に保守的だと言われている. つまり、所有権エラーによって弾かれた箇所は、実際に実行させた時に必ずエラーになるとは限らない. ただ、今回のOption::wrap()のように所々で使用者にその保守的なチェックをうまく回避するためのAPIをRustは用意してくれている.

*1:Option::take()内部ではmem::replace()というメモリを直接操作するAPIが呼ばれている

/* -----codeの行番号----- */