如何从&Vec <T>或Vec <&T>创建&T的迭代器?

时间:2019-09-22 13:33:52

标签: reference rust iterator traits

我有一个带有两个变体的枚举。要么包含对Vec的{​​{1}}的引用,要么包含对String s的Vec的引用:

String

我想遍历该枚举中对enum Foo<'a> { Owned(&'a Vec<String>), Refs(Vec<&'a String>), } 的引用。

我试图在String上实现一个方法,但是不知道如何使它返回正确的迭代器:

Foo

playground

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循环中使用它。

5 个答案:

答案 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 TraitBox<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? */,
        }
    }
}

Playground

答案 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>>`

您可以通过itertoolseither条板箱来解决此错误,这些条板箱包含一个名为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()),
    }
}

playground