What is the difference between iter and into_iter?

后端 未结 4 940
情书的邮戳
情书的邮戳 2020-11-22 14:18

I am doing the Rust by Example tutorial which has this code snippet:

// Vec example
let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];

// `iter()` for vecs         


        
4条回答
  •  广开言路
    2020-11-22 14:50

    I think there's something to clarify a bit more. Collection types, such as Vec and VecDeque, have into_iter method that yields T because they implement IntoIterator. There's nothing to stop us to create a type Foo if which is iterated over, it will yield not T but another type U. That is, Foo implements IntoIterator.

    In fact, there are some examples in std: &Path implements IntoIterator and &UnixListener implements IntoIterator>.


    The difference between into_iter and iter

    Back to the original question on the difference between into_iter and iter. Similar to that others have pointed out, the difference is that into_iter is a required method of IntoIterator which can yield any type specified in IntoIterator::Item. Typically, if a type implements IntoIterator, by convention it has also two ad-hoc methods: iter and iter_mut which yield &I and &mut I, respectively.

    What it implies is that we can create a function that receives a type that has into_iter method (i.e. it is an iterable) by using a trait bound:

    fn process_iterable(iterable: I) {
        for item in iterable {
            // ...
        }
    }
    

    However, we can't* use a trait bound to require a type to have iter method or iter_mut method, because they're just conventions. We can say that into_iter is more widely useable than iter or iter_mut.

    Alternatives to iter and iter_mut

    Another interesting to observe is that iter is not the only way to get an iterator that yields &T. By convention (again), collection types SomeCollection in std which have iter method also have their immutable reference types &SomeCollection implement IntoIterator. For example, &Vec implements IntoIterator, so it enables us to iterate over &Vec:

    let v = vec![1, 2];
    
    // Below is equivalent to: `for item in v.iter() {`
    for item in &v {
        println!("{}", item);
    }
    

    If v.iter() is equivalent to &v in that both implement IntoIterator, why then does Rust provide both? It's for ergonomics. In for loops, it's a bit more concise to use &v than v.iter(); but in other cases, v.iter() is a lot clearer than (&v).into_iter():

    let v = vec![1, 2];
    
    let a: Vec = v.iter().map(|x| x * x).collect();
    // Although above and below are equivalent, above is a lot clearer than below.
    let b: Vec = (&v).into_iter().map(|x| x * x).collect();
    

    Similarly, in for loops, v.iter_mut() can be replaced with &mut v:

    let mut v = vec![1, 2];
    
    // Below is equivalent to: `for item in v.iter_mut() {`
    for item in &mut v {
        *item *= 2;
    }
    

    When to provide (implement) into_iter and iter methods for a type

    If the type has only one “way” to be iterated over, we should implement both. However, if there are two ways or more it can be iterated over, we should instead provide an ad-hoc method for each way.

    For example, String provides neither into_iter nor iter because there are two ways to iterate it: to iterate its representation in bytes or to iterate its representation in characters. Instead, it provides two methods: bytes for iterating the bytes and chars for iterating the characters, as alternatives to iter method.


    * Well, technically we can do it by creating a trait. But then we need to impl that trait for each type we want to use. Meanwhile, many types in std already implement IntoIterator.

提交回复
热议问题