接受字符串迭代器的函数的正确签名

问题描述

我对用于生成字符串切片的迭代器的正确类型感到困惑。

fn print_strings<'a>(seq: impl IntoIterator<Item = &'a str>) {
    for s in seq {
        println!("- {}",s);
    }
}

fn main() {
    let arr: [&str; 3] = ["a","b","c"];
    let vec: Vec<&str> = vec!["a","c"];
    let it: std::str::Split<'_,char> = "a b c".split(' ');

    print_strings(&arr);
    print_strings(&vec);
    print_strings(it);
}

使用 <Item = &'a str>arrvec 调用不会编译。相反,如果我使用 <Item = &'a'a str>,它们可以工作,但 it 调用无法编译。

当然,我也可以使 Item 类型通用,并且这样做

fn print_strings<'a,I: std::fmt::display>(seq: impl IntoIterator<Item = I>)

但它变得愚蠢。肯定必须有一个规范的“字符串值迭代器”类型吗?

解决方法

您看到的错误是意料之中的,因为 seq&Vec<&str> 并且 &Vec<T> 使用 IntoIterator 实现了 Item=&T,因此使用您的代码,您最终会得到Item=&&str 在所有情况下您都期望它是 Item=&str

执行此操作的正确方法是扩展 Item 类型,以便它可以同时处理 &str &&str。您可以通过使用更多泛型来做到这一点,例如

fn print_strings(seq: impl IntoIterator<Item = impl AsRef<str>>) {
    for s in seq {
        let s = s.as_ref();
        println!("- {}",s);
    }
}

这要求 Item 是您可以从中检索 &str 的东西,然后在您的循环中 .as_ref() 将返回您正在寻找的 &str

这还有一个额外的好处,即您的代码也适用于 Vec<String> 和任何其他实现 AsRef<str> 的类型。

,

TL;DR 您使用的签名很好,是调用者为迭代器提供了错误的 Item - 但可以轻松修复。

如另一个答案中所述,print_string() 不接受 &arr&vec,因为 IntoIterator for &[T; n]&Vec<T> yield 引用 T。这是因为 &Vec 本身是一个引用,不允许使用 Vec 以将 T 值移出它。它可以做的是分发对位于 T 内的 Vec 项的引用,即类型为 &T 的项。对于不编译的调用者,容器包含 &str,因此它们的迭代器分发 &&str

除了使 print_string() 更通用之外,另一种解决问题的方法是一开始就正确调用它。例如,这些都编译:

print_strings(arr.iter().map(|sref| *sref));
print_strings(vec.iter().copied());
print_strings(it);

Playground

iter() 是切片提供的方法(因此可用于数组和 Vec),它遍历对元素的引用,就像 IntoIterator&Vec 一样。我们显式调用它是为了能够调用 map() 以显而易见的方式将 &&str 转换为 &str - 通过使用 * 运算符取消引用 &&strcopied() 迭代器适配器是另一种表达方式,可能比 map(|x| *x) 更隐蔽。 (还有 cloned(),相当于 map(|x| x.clone())。)

如果您有一个包含 print_strings() 值的容器,也可以调用 String

let v = vec!["foo".to_owned(),"bar".to_owned()];
print_strings(v.iter().map(|s| s.as_str()));