What is the correct way to return an Iterator (or any other trait)?

后端 未结 1 536
既然无缘
既然无缘 2020-11-21 22:31

The following Rust code compiles and runs without any issues.

fn main() {
    let text = \"abc\";
    println!(\"{}\", text.split(\' \').take(2).count());
}
         


        
1条回答
  •  清酒与你
    2020-11-21 22:45

    I've found it useful to let the compiler guide me:

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

    Compiling gives:

    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> {
      |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    Following the compiler's suggestion and copy-pasting that as my return type (with a little cleanup):

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

    The problem is that you cannot return a trait like Iterator because a trait doesn't have a size. That means that Rust doesn't know how much space to allocate for the type. You cannot return a reference to a local variable, either, so returning &dyn Iterator is a non-starter.

    Impl trait

    As of Rust 1.26, you can use impl trait:

    fn to_words<'a>(text: &'a str) -> impl Iterator {
        text.split(' ')
    }
    
    fn main() {
        let text = "word1 word2 word3";
        println!("{}", to_words(text).take(2).count());
    }
    

    There are restrictions on how this can be used. You can only return a single type (no conditionals!) and it must be used on a free function or an inherent implementation.

    Boxed

    If you don't mind losing a little bit of efficiency, you can return a Box:

    fn to_words<'a>(text: &'a str) -> Box + 'a> {
        Box::new(text.split(' '))
    }
    
    fn main() {
        let text = "word1 word2 word3";
        println!("{}", to_words(text).take(2).count());
    }
    

    This is the primary option that allows for dynamic dispatch. That is, the exact implementation of the code is decided at run-time, rather than compile-time. That means this is suitable for cases where you need to return more than one concrete type of iterator based on a condition.

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

    Type alias

    As pointed out by 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());
    }
    

    Dealing with closures

    When impl Trait isn't available for use, closures make things more complicated. Closures create anonymous types and these cannot be named in the return type:

    fn odd_numbers() -> () {
        (0..100).filter(|&v| v % 2 != 0)
    }
    
    found type `std::iter::Filter, [closure@src/lib.rs:4:21: 4:36]>`
    

    In certain cases, these closures can be replaced with functions, which can be named:

    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, for<'r> fn(&'r i32) -> bool>`
    

    And following the above advice:

    use std::{iter::Filter, ops::Range};
    
    type Odds = Filter, fn(&i32) -> bool>;
    
    fn odd_numbers() -> Odds {
        fn f(&v: &i32) -> bool {
            v % 2 != 0
        }
        (0..100).filter(f as fn(v: &i32) -> bool)
    }
    

    Dealing with conditionals

    If you need to conditionally choose an iterator, refer to Conditionally iterate over one of several possible iterators.

    0 讨论(0)
提交回复
热议问题