我有一个带有两个变体的枚举。要么包含对Vec
的{{1}}的引用,要么包含对String
s的Vec
的引用:
String
我想遍历该枚举中对enum Foo<'a> {
Owned(&'a Vec<String>),
Refs(Vec<&'a String>),
}
的引用。
我试图在String
上实现一个方法,但是不知道如何使它返回正确的迭代器:
Foo
在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
特征,我就可以在IntoIterator
循环中使用它。
答案 0 :(得分:6)
您不能仅为此使用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);
}
}
答案 1 :(得分:4)
有一个方便的板条箱auto_enums
,它可以为您生成一个类型,因此一个函数可以具有多个返回类型,只要它们实现相同的特征即可。它与Denys Séguret's answer中的代码类似,不同的是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"
答案 2 :(得分:2)
如果您不想实现自己的迭代器,则需要为此进行动态调度,因为您希望根据枚举变量返回不同的迭代器。
我们需要一个特征对象(&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>
,因此实际上并没有复制任何内容:)
答案 3 :(得分:1)
您应该首先了解几件事:
Box<dyn Iterator<Item = &'a _>>
,但是如果这会导致可量化的性能下降,请随时使用enum
。self
的生存期,因为如果我们返回一个生存期为'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!
),
}
}
}
这些类型并不是真的像铁锈一样(或更正式地讲,是惯用),所以这里是使用切片和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? */,
}
}
}
答案 4 :(得分:1)
理想情况是:
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
或either
条板箱来解决此错误,这些条板箱包含一个名为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()),
}
}