如何使用迭代器特质构建通用的API?

6

我可能会看不到树林而只看到树木,但我想知道如何设计我的方法,使其不针对硬集合类型而是针对迭代器。考虑以下这个方法。

pub fn print_strings(strings: Vec<String>) {
    for val in strings.iter() {
        println!("{}", val);
    }
}

显然,如果我想要将其与 HashSet HashMap 一起使用,这就不够用了。因此,我尝试了以下方法:
use std::collections::*;

fn main () {
    let strings = vec!("Foo", "Bar");

    let mut more_strings = HashMap::new();
    more_strings.insert("foo", "bar");
    more_strings.insert("bar", "foo");

    print_strings(&strings.iter());
    print_strings(&more_strings.values())
}

fn print_strings(strings: &Iterator<Item=&str>) {
    for val in strings {
        println!("{}", val);
    }
}

Playpen(也可用于查看冗长的编译器错误)

http://is.gd/EYIK11

不幸的是,这似乎也没有起作用。我错过了什么?


请包括错误消息。它会告诉您实现有什么问题。您可能需要查看您所拥有的迭代器的 Iterator::Item 类型。 - Shepmaster
我认为那不是一个好主意,因为它非常冗长。你可以点击playpen链接查看整个编译器错误输出。 - Christoph
当你在 Vec<T> 上调用 .iter() 时,你会得到一个 Iterator<Item=&T>。因此,当你在 Vec<&str> 上调用 .iter() 时,你会得到一个 Iterator<Item=&&str>,而不是 Iterator<Item=&str>。你应该查看 Iterator.cloned() 方法,它应该有助于解决你的问题。 - Adrian
另外,请注意,为了遍历迭代器,您必须能够改变它(拥有迭代器或具有可变引用)。因此,仅拥有不可变引用是无用的。我建议将迭代器值移入print_strings而不是通过引用传递。如果您想要使用trait对象来实现这一点,可以使用Box,但是将print_strings作为通用函数可能更容易些。 - Adrian
顺便提一下,这是您的代码的一个可用版本:http://is.gd/KUItXM - Adrian
太棒了,我得去了解一下这个“cloned”方法。为什么不把它发布为答案呢? - Christoph
3个回答

7
更好的是,你可以执行
fn print_strings<Iterable>(strings: Iterable)
    where Iterable: IntoIterator,
          Iterable::Item: AsRef<str>
{
    for val in strings {
        println!("{}", val.as_ref());
    }
}

这意味着您可以使用&mut Iterator实现动态分派,也可以使用具体的迭代器或集合类型进行静态分派。此外,迭代器类型可以是任何可以简单转换为&str的类型,包括但不限于&str&&str甚至String

(感谢Shepmaster进行改进。)

print_strings(&strings);
print_strings(strings.iter().map(|s| s.to_owned()));
print_strings(vec![&&&&"xyz"]);
print_strings(strings);
print_strings(more_strings.values());

4
当你在 Vec<T> 上调用 .iter() 时,你会得到一个 Iterator<Item=&T>。因此,当你在 Vec<&str> 上调用 .iter() 时,你会得到一个 Iterator<Item=&&str>,而不是一个 Iterator<Item=&str>。你应该看一下 Iterator.cloned() 方法,它应该有助于解决你的问题。
此外,请注意,要迭代通过一个迭代器,你必须能够改变它(拥有迭代器或者拥有可变的引用)。所以,仅仅拥有一个不可变的引用是没用的。我建议将迭代器值移到 print_strings 中,而不是通过引用传递它。如果你想为此使用 trait 对象,你可以使用 Box,但是直接让 print_strings 成为一个泛型函数可能更容易些。

4

playpen
首先,您期望的是Iterator<Item=&str>,但实际上是Iterator<Item=&&str>
其次,您尝试调用.iter(),但Iterator没有此方法。 您可以简单地移除.iter()并接收(和发送)&mut Iterator<...>以使for循环工作(for循环需要实现IntoIterator的东西,&mut Iterator就是那个东西)。
添加生命周期,就完成了! :)
此外,我建议使用静态分派。您可以在我提供的示例中看到。


好的,那个playpen清楚地解释了它。关于静态分派,我为这个例子节省了一些按键 :P - Christoph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接