Why can't I use `&Iterator` as an iterator?

前端 未结 3 2126
心在旅途
心在旅途 2021-01-04 13:29

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

fn max_width(st         


        
相关标签:
3条回答
  • 2021-01-04 14:11

    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

    0 讨论(0)
  • 2021-01-04 14:23

    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

    0 讨论(0)
  • 2021-01-04 14:27

    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.
    0 讨论(0)
提交回复
热议问题