HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustCritical

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

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
returnanyiteratorothercorrectthewaywhattrait

Problem

The following Rust code compiles and runs without any issues.

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


After that, I tried something like this .... but it didn't compile

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

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


The main problem is that I'm not sure what return type the function to_words() should have. The compiler says:

error[E0599]: no method named count found for type std::iter::Take> 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 : std::marker::Sized
std::iter::Take> : std::iter::Iterator


What would be the correct code to make this run? .... and where is my knowledge gap?

Solution

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
help: try adding a semicolon
|
5 | text.split(' ');
| ^
help: try adding a return type
|
3 | fn to_words(text: &str) -> std::str::Split {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^


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 {
    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(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(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(str::Split);

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

    fn next(&mut self) -> Option {
        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 = str::Split;

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 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.

Code Snippets

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

fn to_words(text: &str) -> str::Split<'_, char> {
    text.split(' ')
}
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());
}
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());
}
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());
}

Context

Stack Overflow Q#27535289, score: 286

Revisions (0)

No revisions yet.