patternrustCritical
What is the correct way to return an Iterator (or any other trait)?
Viewed 0 times
returnanyiteratorothercorrectthewaywhattrait
Problem
The following Rust code compiles and runs without any issues.
After that, I tried something like this .... but it didn't compile
The main problem is that I'm not sure what return type the function
What would be the correct code to make this run? .... and where is my knowledge gap?
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:
Compiling gives:
Following the compiler's suggestion and copy-pasting that as my return type (with a little cleanup):
The problem is that you cannot return a trait like
Impl trait
As of Rust 1.26, you can use
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
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
Type alias
As pointed out by reem
Dealing with closures
When
In certain cases, these closures can be replaced with functions, which can be named:
And following the above advice:
Dealing with conditionals
If you need to conditionally choose an iterator, refer to Conditionally iterate over one of several possible iterators.
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.