如何从 &Vec<T> 或 Vec<&T> 创建 &T 的迭代器?
How can I create an iterator of &T from either a &Vec<T> or Vec<&T>?
我有一个有两个变体的枚举。它要么包含对 String
的 Vec
的引用,要么包含对 String
的 Vec
的引用:
enum Foo<'a> {
Owned(&'a Vec<String>),
Refs(Vec<&'a String>),
}
我想遍历对该枚举中 String
的引用。
我试图在 Foo
上实现一个方法,但不知道如何使它 return 成为正确的迭代器:
impl<'a> Foo<'a> {
fn get_items(&self) -> Iter<'a, String> {
match self {
Foo::Owned(v) => v.into_iter(),
Foo::Refs(v) => /* what to put here? */,
}
}
}
fn main() {
let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()];
let foo = Foo::Owned(&test);
for item in foo.get_items() {
// item should be of type &String here
println!("{:?}", item);
}
}
在 &Vec<T>
和 Vec<&T>
上实现这种抽象的惯用方法是什么? get_items
也可以 return 不同的东西,只要它实现了 IntoIterator
特性,这样我就可以在 for
循环中使用它。
如果您不想实现自己的迭代器,则需要对此进行动态分派,因为您希望根据枚举变量 return 不同的迭代器。
我们需要一个 trait 对象(&dyn Trait
、&mut dyn Trait
或 Box<dyn Trait>
)来使用动态调度:
impl<'a> Foo<'a> {
fn get_items(&'a self) -> Box<dyn Iterator<Item = &String> + 'a> {
match self {
Foo::Owned(v) => Box::new(v.into_iter()),
Foo::Refs(v) => Box::new(v.iter().copied()),
}
}
}
.copied()
将 Iterator<Item = &&String>
转换为 Iterator<Item = &String>
,所以这实际上并没有复制任何东西:)
您不能为此只使用 std::slice::Iter
类型。
如果您不想复制字符串或向量,则必须实现自己的迭代器,例如:
struct FooIter<'a, 'b> {
idx: usize,
foo: &'b Foo<'a>,
}
impl<'a, 'b> Iterator for FooIter<'a, 'b> {
type Item = &'a String;
fn next(&mut self) -> Option<Self::Item> {
self.idx += 1;
match self.foo {
Foo::Owned(v) => v.get(self.idx - 1),
Foo::Refs(v) => v.get(self.idx - 1).map(|s| *s),
}
}
}
impl<'a, 'b> Foo<'a> {
fn get_items(&'b self) -> FooIter<'a, 'b> {
FooIter { idx: 0, foo: self }
}
}
fn main() {
let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()];
let foo = Foo::Owned(&test);
for item in foo.get_items() {
println!("{:?}", item);
}
let a = "a".to_string();
let b = "b".to_string();
let test: Vec<&String> = vec![&a, &b];
let foo = Foo::Refs(test);
for item in foo.get_items() {
println!("{:?}", item);
}
}
您首先应该了解的几件事:
- 您肯定会拥有两个不同的迭代器,因为它们是您要迭代的不同基类型。因此,我将使用
Box<dyn Iterator<Item = &'a _>>
,但如果这会导致可量化的性能下降,请随意使用 enum
。
- 这里需要引入
self
的生命周期,因为如果我们return一个生命周期为'a
的迭代器,但是'a > 'self
呢?因此,我们创造了新的生命(我称之为 'b
。)。
- 现在只是与参考层争论的问题:
这是使用原始类型的实现:
enum Foo<'a> {
Owned(&'a Vec<String>),
Refs(Vec<&'a String>)
}
impl<'a> Foo<'a> {
fn get_items<'b>(&'b self) -> Box<dyn Iterator<Item = &'a String> + 'b> {
match self {
Foo::Owned(v) => //v: &'a Vec<String>
Box::new(
v.iter() //Iterator<Item = &'a String> -- Good!
),
Foo::Refs(v) => //v: Vec<&'a String>
Box::new(
v.iter() //Iterator<Item = &'b &'a String> -- Bad!
.map(|x| *x) //Iterator<Item = &'a String> -- Good!
),
}
}
}
这些类型并不是真正的 rust-like(或者更正式地说,惯用的),所以这是使用切片和 str
s 的版本:
enum Foo<'a> {
Owned(&'a [String]),
Refs(Vec<&'a str>)
}
impl<'a> Foo<'a> {
fn get_items<'b>(&'b self) -> Box<dyn Iterator<Item = &'a str> + 'b> {
match self {
Foo::Owned(v) =>
Box::new(
v.into_iter()
.map(|x| &**x) //&'a String -> &'a str
),
Foo::Refs(v) =>
Box::new(
v.iter()
.map(|x| *x) //&'b &'a str -> &'a str
)/* what to put here? */,
}
}
}
有一个方便的板条箱,auto_enums
, which can generate a type for you so a function can have multiple return types, as long as they implement the same trait. It's similar to the code in 除了它是由 auto_enum
宏为您完成的:
use auto_enums::auto_enum;
impl<'a> Foo<'a> {
#[auto_enum(Iterator)]
fn get_items(&self) -> impl Iterator<Item = &String> {
match self {
Foo::Owned(v) => v.iter(),
Foo::Refs(v) => v.iter().copied(),
}
}
}
通过在你的 Cargo.toml
:
中添加这个来添加依赖
[dependencies]
auto_enums = "0.6.3"
理想情况下你会想要:
fn get_items(&self) -> impl Iterator<Item = &String> {
match self {
Foo::Owned(v) => v.into_iter(),
Foo::Refs(v) => v.iter().copied(),
}
}
这里调用copied
就是把一个Iterator<Item = &&String>
转换成我们想要的Iterator<Item = &String>
。这不起作用,因为两个火柴臂具有不同的类型:
error[E0308]: match arms have incompatible types
--> src/main.rs:12:30
|
10 | / match self {
11 | | Foo::Owned(v) => v.into_iter(),
| | ------------- this is found to be of type `std::slice::Iter<'_, std::string::String>`
12 | | Foo::Refs(v) => v.iter().copied(),
| | ^^^^^^^^^^^^^^^^^ expected struct `std::slice::Iter`, found struct `std::iter::Copied`
13 | | }
| |_________- `match` arms have incompatible types
|
= note: expected type `std::slice::Iter<'_, std::string::String>`
found type `std::iter::Copied<std::slice::Iter<'_, &std::string::String>>`
由于 itertools
or either
crates, which contain a handy adapter called Either
(*) 允许您在两个迭代器之间动态选择,您可以修复此错误:
fn get_items(&self) -> impl Iterator<Item = &String> {
match self {
Foo::Owned(v) => Either::Left(v.into_iter()),
Foo::Refs(v) => Either::Right(v.iter().copied()),
}
}
我有一个有两个变体的枚举。它要么包含对 String
的 Vec
的引用,要么包含对 String
的 Vec
的引用:
enum Foo<'a> {
Owned(&'a Vec<String>),
Refs(Vec<&'a String>),
}
我想遍历对该枚举中 String
的引用。
我试图在 Foo
上实现一个方法,但不知道如何使它 return 成为正确的迭代器:
impl<'a> Foo<'a> {
fn get_items(&self) -> Iter<'a, String> {
match self {
Foo::Owned(v) => v.into_iter(),
Foo::Refs(v) => /* what to put here? */,
}
}
}
fn main() {
let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()];
let foo = Foo::Owned(&test);
for item in foo.get_items() {
// item should be of type &String here
println!("{:?}", item);
}
}
在 &Vec<T>
和 Vec<&T>
上实现这种抽象的惯用方法是什么? get_items
也可以 return 不同的东西,只要它实现了 IntoIterator
特性,这样我就可以在 for
循环中使用它。
如果您不想实现自己的迭代器,则需要对此进行动态分派,因为您希望根据枚举变量 return 不同的迭代器。
我们需要一个 trait 对象(&dyn Trait
、&mut dyn Trait
或 Box<dyn Trait>
)来使用动态调度:
impl<'a> Foo<'a> {
fn get_items(&'a self) -> Box<dyn Iterator<Item = &String> + 'a> {
match self {
Foo::Owned(v) => Box::new(v.into_iter()),
Foo::Refs(v) => Box::new(v.iter().copied()),
}
}
}
.copied()
将 Iterator<Item = &&String>
转换为 Iterator<Item = &String>
,所以这实际上并没有复制任何东西:)
您不能为此只使用 std::slice::Iter
类型。
如果您不想复制字符串或向量,则必须实现自己的迭代器,例如:
struct FooIter<'a, 'b> {
idx: usize,
foo: &'b Foo<'a>,
}
impl<'a, 'b> Iterator for FooIter<'a, 'b> {
type Item = &'a String;
fn next(&mut self) -> Option<Self::Item> {
self.idx += 1;
match self.foo {
Foo::Owned(v) => v.get(self.idx - 1),
Foo::Refs(v) => v.get(self.idx - 1).map(|s| *s),
}
}
}
impl<'a, 'b> Foo<'a> {
fn get_items(&'b self) -> FooIter<'a, 'b> {
FooIter { idx: 0, foo: self }
}
}
fn main() {
let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()];
let foo = Foo::Owned(&test);
for item in foo.get_items() {
println!("{:?}", item);
}
let a = "a".to_string();
let b = "b".to_string();
let test: Vec<&String> = vec![&a, &b];
let foo = Foo::Refs(test);
for item in foo.get_items() {
println!("{:?}", item);
}
}
您首先应该了解的几件事:
- 您肯定会拥有两个不同的迭代器,因为它们是您要迭代的不同基类型。因此,我将使用
Box<dyn Iterator<Item = &'a _>>
,但如果这会导致可量化的性能下降,请随意使用enum
。 - 这里需要引入
self
的生命周期,因为如果我们return一个生命周期为'a
的迭代器,但是'a > 'self
呢?因此,我们创造了新的生命(我称之为'b
。)。 - 现在只是与参考层争论的问题:
这是使用原始类型的实现:
enum Foo<'a> {
Owned(&'a Vec<String>),
Refs(Vec<&'a String>)
}
impl<'a> Foo<'a> {
fn get_items<'b>(&'b self) -> Box<dyn Iterator<Item = &'a String> + 'b> {
match self {
Foo::Owned(v) => //v: &'a Vec<String>
Box::new(
v.iter() //Iterator<Item = &'a String> -- Good!
),
Foo::Refs(v) => //v: Vec<&'a String>
Box::new(
v.iter() //Iterator<Item = &'b &'a String> -- Bad!
.map(|x| *x) //Iterator<Item = &'a String> -- Good!
),
}
}
}
这些类型并不是真正的 rust-like(或者更正式地说,惯用的),所以这是使用切片和 str
s 的版本:
enum Foo<'a> {
Owned(&'a [String]),
Refs(Vec<&'a str>)
}
impl<'a> Foo<'a> {
fn get_items<'b>(&'b self) -> Box<dyn Iterator<Item = &'a str> + 'b> {
match self {
Foo::Owned(v) =>
Box::new(
v.into_iter()
.map(|x| &**x) //&'a String -> &'a str
),
Foo::Refs(v) =>
Box::new(
v.iter()
.map(|x| *x) //&'b &'a str -> &'a str
)/* what to put here? */,
}
}
}
有一个方便的板条箱,auto_enums
, which can generate a type for you so a function can have multiple return types, as long as they implement the same trait. It's similar to the code in auto_enum
宏为您完成的:
use auto_enums::auto_enum;
impl<'a> Foo<'a> {
#[auto_enum(Iterator)]
fn get_items(&self) -> impl Iterator<Item = &String> {
match self {
Foo::Owned(v) => v.iter(),
Foo::Refs(v) => v.iter().copied(),
}
}
}
通过在你的 Cargo.toml
:
[dependencies]
auto_enums = "0.6.3"
理想情况下你会想要:
fn get_items(&self) -> impl Iterator<Item = &String> {
match self {
Foo::Owned(v) => v.into_iter(),
Foo::Refs(v) => v.iter().copied(),
}
}
这里调用copied
就是把一个Iterator<Item = &&String>
转换成我们想要的Iterator<Item = &String>
。这不起作用,因为两个火柴臂具有不同的类型:
error[E0308]: match arms have incompatible types
--> src/main.rs:12:30
|
10 | / match self {
11 | | Foo::Owned(v) => v.into_iter(),
| | ------------- this is found to be of type `std::slice::Iter<'_, std::string::String>`
12 | | Foo::Refs(v) => v.iter().copied(),
| | ^^^^^^^^^^^^^^^^^ expected struct `std::slice::Iter`, found struct `std::iter::Copied`
13 | | }
| |_________- `match` arms have incompatible types
|
= note: expected type `std::slice::Iter<'_, std::string::String>`
found type `std::iter::Copied<std::slice::Iter<'_, &std::string::String>>`
由于 itertools
or either
crates, which contain a handy adapter called Either
(*) 允许您在两个迭代器之间动态选择,您可以修复此错误:
fn get_items(&self) -> impl Iterator<Item = &String> {
match self {
Foo::Owned(v) => Either::Left(v.into_iter()),
Foo::Refs(v) => Either::Right(v.iter().copied()),
}
}