当需要替换某些项目时,实现“IntoIterator”的惯用方法是什么?

What is the idiomatic way to implement `IntoIterator` when some items need to be substituted?

我有一个这样的自定义集合:

struct VecChoice<T> {
    v1: Vec<T>,
    v2: Vec<T>,
    use_v1: Vec<bool>,
}

在 impl 中,我可以像这样迭代这个集合:

fn foo(&self, ...) {
    let item_refs: Vec<_> = (0..self.v1.len()).map(|i| {
        if self.use_v1[i] {
            &self.v1[i]
        } else {
            &self.v2[i]
        }
    });
    // ... do whatever I want with chosen references
}

但是,我无法使其可迭代:

impl<'a, T> IntoIterator for &'a VecChoice<T> {
    type Item = &'a T;

    // this fails because the trait `Sized` is not implemented for `(dyn FnMut(usize) -> Self::Item + 'static)`
    type IntoIter = Map<usize, dyn FnMut(usize) -> Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        (0..self.v1.len()).map(|i| {
            if self.use_v1[i] {
                &self.v1[i]
            } else {
                &self.v2[i]
            }
        })
    }
}

我可能会像上面那样将结果收集到 Vec<&T> 中,然后使用它的 into_iter,但我怀疑应该有一种方法可以在不构建中间 Vec 的情况下做到这一点。

可能最简单的方法是使用 .zip() 和 return 来自类型方法的不透明 impl Iterator (这样你就不必写出实际类型):

struct VecChoice<T> {
    v1: Vec<T>,
    v2: Vec<T>,
    use_v1: Vec<bool>,
}

impl<T> VecChoice<T> {
    fn iter(&self) -> impl Iterator<Item = &T> {
        self.v1
            .iter()
            .zip(self.v2.iter())
            .zip(self.use_v1.iter())
            .map(|((v1, v2), use_v1)| if use_v1 { v1 } else { v2 })
    }
}

这将从 v1v2.

遍历所有三个 Vec(实际上是其中最短的)和 return

请注意,我将 use_v1Vec<T> 切换为 Vec<bool>,鉴于您的使用方式,这似乎就是您所拥有的。

尝试从预制集合中创建迭代器通常很诱人,但不幸的是,这在很多时候往往 运行 成为一个实际问题:你需要一些方法来存储一个偏移到该集合中,因此当 next 被调用时,您可以从中提供正确的数据块。因此,您几乎总是需要提供一些自定义迭代器类型。

在这种情况下,您可以这样做:

struct VecChoice<T> {
    v1: Vec<T>,
    v2: Vec<T>,
    use_v1: Vec<bool>,
}

struct VecChoiceIter<'a, T> {
    off: usize,
    collection: &'a VecChoice<T>,
}

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

    fn next(&mut self) -> Option<Self::Item> {
        let off = self.off;
        self.off += 1;
        if *self.collection.use_v1.get(off)? {
            self.collection.v1.get(off)
        } else {
            self.collection.v2.get(off)
        }
    }
}

impl<'a, T> IntoIterator for &'a VecChoice<T> {
    type Item = &'a T;

    type IntoIter = VecChoiceIter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        VecChoiceIter {
            off: 0,
            collection: self,
        }
    }
}

请注意,在这种情况下,我已将 use_v1 切换为 Vec<bool>,因为这不是 C 并且在条件中只能使用布尔值。

您也可以预先进行转换并将其存储在自己的 Vec 中,但根据我的经验,人们不希望创建迭代器,无论是通过调用 iter 还是 [=16] =],要贵。迭代器在 Rust 中是非常基础的,因此人们很常见地创建很多迭代器,通常是隐式的,并且在许多情况下不希望使这些函数变得昂贵。

您传递给 map 的闭包实际上有一个大小。但问题是这种类型不可命名。您已尝试使用 dyn 来解决这个问题,这不是正确的解决方案,因为闭包 大小,但 dyn 使它成为'吨。 dyn 如果有不同的可能大小是合适的,但是你必须把它放在某种指针后面,这样 IntoIter 类型就是 Sized.

在这种情况下,手动实施 Iterator 可能比使用组合器更好。

struct VecChoiceIter<'a, T> {
    index: usize,
    vec_choice: &'a VecChoice<T>,
}

impl<'a, T> Iterator for VecChoiceIter<'a, T> {
    type Item = &'a T;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index == self.vec_choice.v1.len() {
            None
        } else {
            let i = self.index;
            self.index += 1;
            let use_v1 = self.vec_choice.use_v1[i];
            if use_v1 {
                Some(&self.vec_choice.v1[i])
            } else {
                Some(&self.vec_choice.v2[i])
            }
        }
    }
}

这为您提供了一个 Sized 和可命名的类型,您可以将其用于 IntoIterator 实现:

impl<'a, T> IntoIterator for &'a VecChoice<T> {
    type Item = &'a T;
    type IntoIter = VecChoiceIter<'a, T>;
    fn into_iter(self) -> Self::IntoIter {
        VecChoiceIter { index: 0, vec_choice: self }
    }
}

一些有趣的 RFC 正在进行中,可以使这项工作更像您最初想要的那样。特别是 RFC-2515. This would let you write your IntoIterator implementation as you originally tried, but without having to name the type (playground - nightly):

impl<'a, T> IntoIterator for &'a VecChoice<T> {
    type Item = &'a T;

    // This is an "existential" type. That is, tell the compiler that there is 
    // exactly one possibility for what this type can be, which it can infer 
    // from the usage.
    type IntoIter = impl Iterator<Item = Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        (0..self.v1.len()).map(move |i| {
            if self.use_v1[i] {
                &self.v1[i]
            } else {
                &self.v2[i]
            }
        })
    }
}