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

How can I create an iterator of &T from either a &Vec<T> or Vec<&T>?

我有一个有两个变体的枚举。它要么包含对 StringVec 的引用,要么包含对 StringVec 的引用:

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);
    }
}

playground

&Vec<T>Vec<&T> 上实现这种抽象的惯用方法是什么? get_items 也可以 return 不同的东西,只要它实现了 IntoIterator 特性,这样我就可以在 for 循环中使用它。

如果您不想实现自己的迭代器,则需要对此进行动态分派,因为您希望根据枚举变量 return 不同的迭代器。

我们需要一个 trait 对象&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>,所以这实际上并没有复制任何东西:)

您不能为此只使用 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(或者更正式地说,惯用的),所以这是使用切片和 strs 的版本:

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

有一个方便的板条箱,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()),
    }
}

playground