Optional .collect() when combinator is last operation

Let's take this example: 

let numbers = vec![1, 2, 3, 4, 5];
let squares_of_evens: Vec<i32> = numbers.iter()
    .filter(|&n| n % 2 == 0)
    .map(|&n| n * n)
    .collect();
  • Since .map() is the last operation, we can skip calling .collect()
  • Hence Vec<i32> part wouldn't be needed anymore, since we already know that current vector comprises of i32 elements, so it would be auto inferred by compiler.
  • "Iterators are lazy", but since there is an assignment to squares_of_evens, hence we could consider it "consumed"
  • .collect() can still be used when we explicitly want to convert it into a different type (e.g. let squares_of_evens: Vec<u32>)

Thus we could have such updated syntax:

let numbers = vec![1, 2, 3, 4, 5];
let squares_of_evens = numbers.iter()
    .filter(|&n| n % 2 == 0)
    .map(|&n| n * n);

The same approach could also be applicable to other similar combinators, e.g., filter

let numbers = vec![1, 2, 3, 4, 5];
let even_numbers = numbers.iter()
    .filter(|&n| n % 2 == 0);
    // .collect() not needed here
    // since `.filter` is the last operation, it can auto return a vector of i32 elements
   // as nothing has changed during .filter operation

No, this is both unfeasible and undesirable.

First off, how would you even implement it? Iterators are normal library code, they have no idea what’s the "last operation" in a chain of calls and can’t return different types based on such things.

Second, there are many different types you can collect into; in fact an unbounded number because any type can implement FromIter and become collect-able. Vec is just the most common use case.

And third, we don’t want any sort of eager auto-collecting. Iterators are lazy for a reason, and often it’s possible to avoid the cost of materializing the iterator at all.

14 Likes

The compiler doesn't know that you wanted it to contain u32 elements. In fact if you don't specify anything the compiler will fallback to assuming i32 elements.

This is already valid code, and the type of squares_of_evens would be something like Map<Filter<std::slice::Iter<'_, {some integer type}>, {some anonymous closure}>, {some other anonymous closure}>, hence it cannot be Vec<u32> or Vec<i32>.

7 Likes

This ultimatly means to introduce some kind of "collect coercion" similar to deref coercion which executes a lot of hidden code with idiomatic trait implementations.

This would make this silly code stop working - I map an infinite iterator, and then do an operation later that gets me a finite result.

If you make map implicitly call collect() when it's the last operation in a series of transforms, then the map operation will enter an infinite loop. Further, as written, it'll stop compiling, because there is no Vec::take_while operation.

4 Likes

That's correct. Description updated.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.