12

I have the following function that's supposed to find and return the longest length of a String given an Iterator:

fn max_width(strings: &Iterator<Item = &String>) -> usize {
    let mut max_width = 0;
    for string in strings {
        if string.len() > max_width {
            max_width = string.len();
        }
    }
    return max_width;
}

However, the compiler gives me the following error:

error[E0277]: the trait bound `&std::iter::Iterator<Item=&std::string::String>: std::iter::Iterator` is not satisfied
 --> src/main.rs:3:19
  |
3 |     for string in strings {
  |                   ^^^^^^^ `&std::iter::Iterator<Item=&std::string::String>` is not an iterator; maybe try calling `.iter()` or a similar method
  |
  = help: the trait `std::iter::Iterator` is not implemented for `&std::iter::Iterator<Item=&std::string::String>`
  = note: required by `std::iter::IntoIterator::into_iter`

I'm new to Rust, and terribly confused by this, since I thought I was explicitly passing in an iterator. Calling strings.iter() tells me that it is not implemented, and calling strings.into_iter() sends me down a mutability rabbit-hole, and I certainly don't want to mutate the passed argument.

How can I iterate over my strings?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
pjf
  • 5,993
  • 25
  • 42

3 Answers3

14

Your code fails because Iterator is not the same as &Iterator. You can fix this if you pass Iterator to your function, but since Iterator is a trait, the size cannot be determined (You can't know, what Iterator you are passing). The solution is to pass anything that implements Iterator:

fn max_width<'a>(strings: impl Iterator<Item = &'a String>) -> usize

playground


For more experienced Rust users:

The most generic way is probably this:

fn max_width<T: AsRef<str>>(strings: impl IntoIterator<Item = T>) -> usize {
    let mut max_width = 0;
    for string in strings {
        let string = string.as_ref();
        if string.len() > max_width {
            max_width = string.len();
        }
    }
    max_width
}

playground

However, you can also use

fn max_width<T: AsRef<str>>(strings: impl IntoIterator<Item = T>) -> usize {
    strings
        .into_iter()
        .map(|s| s.as_ref().len())
        .max()
        .unwrap_or(0)
}

playground

Tim Diekmann
  • 7,755
  • 11
  • 41
  • 69
  • This works, but I'm struggling to understand the semantics. I get that `Iterator` is a trait (and thus could be anything that implements that trait), but I'm confused about the `impl` keyword in this context. How does that end up with us having a known size when we didn't have it before? – pjf Aug 09 '18 at 04:14
  • I recommend reading [this](https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/traits/impl-trait.html). Basically, it's the same as `fn max_width<'a, T: Iterator(strings: T) -> usize`. – Tim Diekmann Aug 09 '18 at 04:20
  • 3
    Oh! So `Iterator` *is the trait itself*, and `impl Iterator` is something that implements that trait. Thank you so much!! – pjf Aug 09 '18 at 05:26
  • 1
    Thanks @Tom. I think [this section](https://doc.rust-lang.org/edition-guide/rust-2018/trait-system/impl-trait-for-returning-complex-types-with-ease.html) matches the old link. – Tim Diekmann Oct 10 '19 at 20:03
6

The other answers show you how to accept an iterator, but gloss over answering your actual question:

Why can't I use &Iterator<Item = &String> as an iterator?

Amusingly enough, you've prevented it yourself:

and I certainly don't want to mutate the passed argument

Iterators work by mutating their target — that's how the iterator can change to return a new value for each call!

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    //       ^^^
}

By taking in an immutable trait object, it's impossible for your iterator to update itself, thus it's impossible to actually iterate.

The absolute smallest thing you can do to make your code compile is to accept a mutable reference:

fn max_width(strings: &mut dyn Iterator<Item = &String>) -> usize

However, I'd probably write the function as:

fn max_width<I>(strings: I) -> usize
where
    I: IntoIterator,
    I::Item: AsRef<str>,
{
    strings
        .into_iter()
        .map(|s| s.as_ref().len())
        .max()
        .unwrap_or(0)
}
  1. Don't use an explicit return
  2. Use iterator combinators like map and max
  3. Use Option::unwrap_or to provide a default.
  4. Use IntoIterator to accept anything that can be made into an iterator.
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    I prefer this answer since it also offers a solution close to OP's initial intent, using dynamic polymorphism. – Thierry Aug 09 '18 at 12:19
1

If you don't specifically need the generality of iterating over any given iterator, a simpler way to write the function is to have your max_width function take a &[&str] (A slice of string slices) instead. You can use a slice in a for loop because Rust knows how to turn that into an iterator (it implements IntoIterator trait):

fn max_width(strings: &[&str]) -> usize {
    let mut max_width = 0;
    for string in strings {
        if string.len() > max_width {
            max_width = string.len();
        }
    }
    return max_width;
}

fn main() {
    let strings = vec![
        "following",
        "function",
        "supposed",
        "return",
        "longest",
        "string",
        "length"
    ];

    let max_width = max_width(&strings);

    println!("Longest string had size {}", max_width);
}

// OUTPUT: Longest string had size 9

Playground here

Matt Harrison
  • 13,381
  • 6
  • 48
  • 66
  • Thanks, I'll update. Do `&String`'s also deref to `&str` and would you not pass that instead? I thought maybe OP being new to Rust just wanted to iterate a bunch of strings and a Vec seemed like an easy way to do that. The generic solution is interesting though. – Matt Harrison Aug 09 '18 at 04:07
  • My apologies, I should have added my use case! I was unpacking fields from a vector of structs (`max_width(recipes.iter().map(|x| &x.name))`) so I already had an iterator, hence the function using that instead of a vector. – pjf Aug 09 '18 at 07:54
  • 1
    @pjf If you may consider replacing it with this: `recipes.iter().map(|x| x.name.len()).max().unwrap_or(0)` – Tim Diekmann Aug 09 '18 at 08:29
  • You may add your use case to the question as well :) – Tim Diekmann Aug 09 '18 at 08:30