当需要替换某些项目时,实现“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 })
}
}
这将从 v1
或 v2
.
遍历所有三个 Vec
(实际上是其中最短的)和 return
请注意,我将 use_v1
从 Vec<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]
}
})
}
}
我有一个这样的自定义集合:
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 })
}
}
这将从 v1
或 v2
.
Vec
(实际上是其中最短的)和 return
请注意,我将 use_v1
从 Vec<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]
}
})
}
}