Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

IterMut

正直に言うと、IterMut はヤバいです。それ自体がかなり突飛な 言い方に思えるかもしれません。だって、Iter とまったく同じはずでしょう!

意味論的にはその通りです。しかし、共有参照と可変参照の性質により、 Iter は「自明」なのに対し、IterMut は本物の魔法です。

重要な洞察は、Iter に対する Iterator の実装から得られます。

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> { /* 処理 */ }
}

これは次のように脱糖できます。

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next<'b>(&'b mut self) -> Option<&'a T> { /* 処理 */ }
}

next のシグネチャは、入力と出力のライフタイムの間に 何の 制約も設けていません!なぜこれが重要なのでしょうか?つまり、 無条件に next を何度でも呼び出せるということです!

let mut list = List::new();
list.push(1); list.push(2); list.push(3);

let mut iter = list.iter();
let x = iter.next().unwrap();
let y = iter.next().unwrap();
let z = iter.next().unwrap();

いいですね!

共有参照については、これは 間違いなく問題ありません。そもそも共有参照の要点は、 同時に大量に持てることだからです。しかし、可変参照は共存 できません。 可変参照の要点は、それが排他的であることです。

その結果、安全なコードを使って IterMut を書くのはかなり難しくなります (それがそもそも何を意味するのかについては、まだ触れていませんが……)。驚くことに、 IterMut は実際、多くの構造に対して完全に安全に実装できます!

まずは Iter のコードをそのまま取り、すべてを可変に変えるところから始めます。

pub struct IterMut<'a, T> {
    next: Option<&'a mut Node<T>>,
}

impl<T> List<T> {
    pub fn iter_mut(&self) -> IterMut<'_, T> {
        IterMut { next: self.head.as_deref_mut() }
    }
}

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;

    fn next(&mut self) -> Option<Self::Item> {
        self.next.map(|node| {
            self.next = node.next.as_deref_mut();
            &mut node.elem
        })
    }
}
> cargo build
error[E0596]: cannot borrow `self.head` as mutable, as it is behind a `&` reference
  --> src/second.rs:95:25
   |
94 |     pub fn iter_mut(&self) -> IterMut<'_, T> {
   |                     ----- help: consider changing this to be a mutable reference: `&mut self`
95 |         IterMut { next: self.head.as_deref_mut() }
   |                         ^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable

error[E0507]: cannot move out of borrowed content
   --> src/second.rs:103:9
    |
103 |         self.next.map(|node| {
    |         ^^^^^^^^^ cannot move out of borrowed content

さて、ここでは2つの異なるエラーが出ているようです。ただし最初のものはとても明確で、 修正方法まで教えてくれています!共有参照を可変参照へ格上げすることはできないので、 iter_mut&mut self を受け取る必要があります。ただの愚かなコピペミスです。

pub fn iter_mut(&mut self) -> IterMut<'_, T> {
    IterMut { next: self.head.as_deref_mut() }
}

もう一方はどうでしょうか?

おっと!実は前の節で iter の実装を書いたときに、うっかりミスをしていました。 それが動いていたのは、単に運が良かっただけです!

ここで初めて Copy の魔法に出会いました。所有権を導入したとき、 何かをムーブすると、それはもう使えなくなると言いました。いくつかの型では、 これは完全に理にかなっています。私たちの良き友人である Box は、ヒープ上のアロケーションを 管理してくれます。そして、そのメモリを解放する必要があると2つのコード片に 思わせたくは絶対にありません。

しかし、他の型ではこれは くだらない ものです。整数には 所有権の意味論はありません。ただの意味のない数値です!これが、整数が Copy としてマークされている理由です。Copy 型は、ビット単位のコピーで完全にコピー可能だと 分かっている型です。そのため、それらには超能力があります。ムーブされても、古い値が まだ 使用可能なのです。その結果、Copy 型なら、参照の中からでも 置換なしにムーブできます!

Rust のすべての数値プリミティブ(i32、u64、bool、f32、char など)は Copy です。 また、そのすべての構成要素が Copy である限り、任意のユーザー定義型も Copy として宣言できます。

このコードが動いていた理由として重要なのは、共有参照も Copy であることです! & は Copy なので、Option<&>同様に Copy です。そのため、self.next.map を 行ったときも、Option がコピーされるだけだったので問題ありませんでした。今はそれができません。 なぜなら &mut は Copy ではないからです(もし &mut をコピーできるなら、同じメモリ位置への &mut が2つできてしまい、これは禁止されています)。代わりに、Option を得るには適切に take すべきです。

fn next(&mut self) -> Option<Self::Item> {
    self.next.take().map(|node| {
        self.next = node.next.as_deref_mut();
        &mut node.elem
    })
}
> cargo build

えっ……すごい。マジかよ!IterMut がそのまま動いた!

これをテストしてみましょう。

#[test]
fn iter_mut() {
    let mut list = List::new();
    list.push(1); list.push(2); list.push(3);

    let mut iter = list.iter_mut();
    assert_eq!(iter.next(), Some(&mut 3));
    assert_eq!(iter.next(), Some(&mut 2));
    assert_eq!(iter.next(), Some(&mut 1));
}
> cargo test

     Running target/debug/lists-5c71138492ad4b4a

running 6 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::iter_mut ... ok
test second::test::into_iter ... ok
test second::test::iter ... ok
test second::test::peek ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured

はい。動いています。

マジかよ。

どういうこと。

いや、実際これは動くことが 想定されている のですが、たいていは何か くだらないものが邪魔をするものです!ここではっきりさせましょう。

私たちは今、単方向連結リストを受け取り、そのリスト内のすべての要素への 可変参照を高々1回ずつ返すコード片を実装しました。 しかも、それを行うことが静的に検証されています。そして完全に安全です。さらに、 何か突飛なことをする必要もありませんでした。

私に言わせれば、これはかなり大きなことです。これが動く理由はいくつかあります。

  • Option<&mut>take するので、その可変参照への排他的アクセスを得られます。 誰かがそれを再び見ることを心配する必要はありません。
  • Rust は、可変参照を指し先の構造体のサブフィールドへ分割してもよいことを理解しています。 なぜなら、「上へ戻る」方法がなく、それらが確実に互いに素だからです。

実は、この基本的なロジックを適用すると、配列や木に対しても安全な IterMut を 得ることができます!さらに、そのイテレータを DoubleEnded にして、イテレータを 前から かつ 後ろから同時に消費することさえできます!うわお!