Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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<Item = &'a str> {
    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<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());
}

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

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<std::ops::Range<{integer}>, [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<std::ops::Range<i32>, for<'r> fn(&'r i32) -> bool>`

And following the above advice:

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

Dealing with conditionals

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


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!