Rustコトハジメ

プログラミング言語Rustと競プロに関する情報をお届けします。

IntoIteratorの前にDerefは呼ばれないことを実験で確かめる

昨日のブログ

www.rustforbeginners.com

の積み残し課題のために実験したら面白いことがわかったので共有します。バージョンは1.33.0-devです。

課題

&Vec<T>からIter<T>&mut Vec<T>からIterMut<T>へのIntoIterator実装がVec<T>に対して行われているのは実質的に不要じゃないの?良く呼ばれるパスに対する最適化かな?

だって、

  1. &Vec<T>から&[T]へのderefが行われる
  2. &[T]からIter<T>へのIntoIteratorが採用される

の2段階で実行されるはずでしょ?

これを実験で確かめることが課題です。

実験

use std::ops::Deref;

struct MyVec<T> {
    inner: Vec<T>,
}

impl<T> Deref for MyVec<T> {
    type Target = Vec<T>;
    fn deref(&self) -> &Vec<T> {
        &self.inner
    }
}

fn main() {
    let v = MyVec {
        inner: vec![1,2,3,4]
    };
    for e in &v {
        println!("{}", e);
    }
}

上のコードは、

  1. &MyVecから&Vecへのderefが行われる
  2. &VecについてIntoIteratorが採用される

ということを狙ったものです。

結果

しかし、目論見は外れ、実際にはコンパイルが通りません。エラーメッセージから、derefが無視されていることがわかります。

error[E0277]: `&MyVec<{integer}>` is not an iterator
  --> prog.rs:18:14
   |
18 |     for e in &v {
   |              ^^ `&MyVec<{integer}>` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `&MyVec<{integer}>`
   = note: required by `std::iter::IntoIterator::into_iter`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

もし、変数に一度束縛してあげることでderefを呼んであげるとコンパイルが通るので、実験コード自体に誤りがあるわけではないと思います。

    let vv: &Vec<i32> = &v;
    for e in vv {
        println!("{}", e);
    }

考察

想像になってしまいますが、この理由については以下のように考えると自分としてはすっきり行きました。

forループは、関数として考えると、

fn forloop<I, F>(it: I, f: F) where
  I: IntoIterator,
  F: FnMut(I::Item)

のように書くことが出来ます。プログラミングRustのユーティリティトレイトの章で、「Rustは、型の不整合を解決するために参照解決型変換を使うが、型変数の制約を満足させるためにこれを用いない」と書いてあるので、今回の例でいうと、「IntoIteratorをひねりだすためにわざわざderefはしない」と考えると納得行きます。