返回迭代器(或任何其他特质)的正确方式是什么?

213
以下 Rust 代码可以编译并且运行没有问题。
fn main() {
    let text = "abc";
    println!("{}", text.split(' ').take(2).count());
}

之后,我尝试了类似这样的代码……但它无法编译。

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

fn to_words(text: &str) -> &Iterator<Item = &str> {
    &(text.split(' '))
}

主要的问题是我不确定函数to_words()应该有什么返回类型。编译器说:

error[E0599]: no method named `count` found for type `std::iter::Take<std::iter::Iterator<Item=&str>>` in the current scope
 --> src/main.rs:3:43
  |
3 |     println!("{}", to_words(text).take(2).count());
  |                                           ^^^^^
  |
  = note: the method `count` exists but the following trait bounds were not satisfied:
          `std::iter::Iterator<Item=&str> : std::marker::Sized`
          `std::iter::Take<std::iter::Iterator<Item=&str>> : std::iter::Iterator`

正确的代码是什么?我在哪方面的知识存在空白?

2个回答

260

我发现让编译器指导我很有用:

fn to_words(text: &str) { // Note no return type
    text.split(' ')
}

编译的结果如下:

error[E0308]: mismatched types
 --> src/lib.rs:5:5
  |
5 |     text.split(' ')
  |     ^^^^^^^^^^^^^^^ expected (), found struct `std::str::Split`
  |
  = note: expected type `()`
             found type `std::str::Split<'_, char>`
help: try adding a semicolon
  |
5 |     text.split(' ');
  |                    ^
help: try adding a return type
  |
3 | fn to_words(text: &str) -> std::str::Split<'_, char> {
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

按照编译器的建议,我将其复制粘贴为我的返回类型(稍作清理):

use std::str;

fn to_words(text: &str) -> str::Split<'_, char> {
    text.split(' ')
}

问题在于你不能返回像 Iterator 这样的 trait,因为 trait 没有大小。这意味着 Rust 不知道为类型分配多少空间。你也不能返回一个局部变量的引用,所以返回 &dyn Iterator 是行不通的。

Impl trait

截至 Rust 1.26 版本,你可以使用impl trait:

fn to_words<'a>(text: &'a str) -> impl Iterator<Item = &'a str> {
    text.split(' ')
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

对于如何使用它存在限制。您只能返回单一类型(不允许条件语句!),并且必须在自由函数或实现内使用。

Boxed

如果您不介意失去一点效率,可以返回一个Box<dyn Iterator>

fn to_words<'a>(text: &'a str) -> Box<dyn Iterator<Item = &'a str> + 'a> {
    Box::new(text.split(' '))
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

这是一个主要选项,允许动态分派。也就是说,代码的确切实现是在运行时而不是编译时决定的。这意味着它适用于需要基于条件返回多个具体类型的迭代器的情况。

Newtype

use std::str;

struct Wrapper<'a>(str::Split<'a, char>);

impl<'a> Iterator for Wrapper<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        self.0.next()
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

fn to_words(text: &str) -> Wrapper<'_> {
    Wrapper(text.split(' '))
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

类型别名

正如reem所指出的

use std::str;

type MyIter<'a> = str::Split<'a, char>;

fn to_words(text: &str) -> MyIter<'_> {
    text.split(' ')
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

如何处理闭包

impl Trait无法使用时,闭包会使事情变得更加复杂。闭包创建匿名类型,这些类型不能在返回类型中命名:

fn odd_numbers() -> () {
    (0..100).filter(|&v| v % 2 != 0)
}
found type `std::iter::Filter<std::ops::Range<{integer}>, [closure@src/lib.rs:4:21: 4:36]>`
在某些情况下,这些闭包可以被替换为函数,并且可以被命名:
fn odd_numbers() -> () {
    fn f(&v: &i32) -> bool {
        v % 2 != 0
    }
    (0..100).filter(f as fn(v: &i32) -> bool)
}
found type `std::iter::Filter<std::ops::Range<i32>, for<'r> fn(&'r i32) -> bool>`

遵循上述建议:

use std::{iter::Filter, ops::Range};

type Odds = Filter<Range<i32>, fn(&i32) -> bool>;

fn odd_numbers() -> Odds {
    fn f(&v: &i32) -> bool {
        v % 2 != 0
    }
    (0..100).filter(f as fn(v: &i32) -> bool)
}

处理条件语句

如果你需要有条件地选择迭代器,请参考“有条件地迭代多个可能的迭代器”


13
虽然包装类型可以很好地隐藏复杂性,但我发现最好只使用“type”别名,因为使用新类型意味着即使基础迭代器实现了“RandomAccessIterator”等特性,您的迭代器也无法实现它。 - reem
6
可以的!类型别名支持泛型参数。例如,许多库都会使用 type LibraryResult<T> = Result<T, LibraryError> 这样的类型别名作为方便,类似于 IoResult<T>,也只是一个类型别名。 - reem
1
请问为什么要在“Box”中添加“'a”生命周期?这是什么意思?我一直以为这只是用于边界,表示“T可能仅依赖于至少与'a'一样长寿命的东西”。 - torkleyy
1
@torkleyy 或许这个链接https://dev59.com/Vobca4cB1Zd3GeqPYaEO或者https://dev59.com/Uobca4cB1Zd3GeqPVmwQ可以回答你的问题?如果不能,我鼓励你搜索你的问题,如果找不到,就提出一个新的问题。 - Shepmaster
仍然不确定如何通过堆栈传递迭代器的方法。仍然不确定impl关键字和boxes之间的交互方式,以及哪种方法更受欢迎以及为什么。仍然不确定迭代器应该在堆栈上扎根的位置,以及这可能会影响哪些传递迭代器的方法是必要的。 - gnkdl_gansklgna
显示剩余5条评论

3
添加到@Shepmaster的答案中。如果您需要闭包并捕获一些参数,可以使用此示例。
fn find_endpoint<'r>(
    descriptors: &'r mut rusb::EndpointDescriptors<'r>,
    direction: rusb::Direction,
) -> FilterMap<
    &mut rusb::EndpointDescriptors<'_>,
    impl FnMut(rusb::EndpointDescriptor<'r>) -> Option<u8>,
> {
    descriptors.filter_map(move |ep| {
        if ep.direction() == direction {
            Some(ep.address())
        } else {
            None
        }
    })
}

我找了好几个月才找到这个解决方案,谢谢伙计! - undefined

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