How Borrowing can create iterator

I am trying to create my own implementation of some example. I met an error but I am not sure to understand the solution.

here the code and the error :

use std::str::FromStr;

fn main() {

    let numbers: Vec<u64> = std::env::args()
        .skip(1)
        .map(|arg| u64::from_str(&arg).expect("Error parsing argument"))
        .collect()
    ;

    if numbers.len() == 0 {
        eprintln!("Usage: <app> NUMBER ...");
        std::process::exit(1);
    }

    println!("{:#?}", numbers);

    let mut first = numbers[0];
    for current in numbers[1..] {
        first = gcd(first, current);
    }

    println!("the gcd is {:#?}", first);
}

fn gcd(mut a: u64, mut b: u64) -> u64 {
    assert!(a != 0 && b != 0);
    while a != 0 {
        if a < b {
            let temp = a;
            a = b;
            b = temp;
        }
        a = a % b;
    }
    b
}

The error :

error[E0277]: the size for values of type `[u64]` cannot be known at compilation time
  --> src/main.rs:19:20
   |
19 |     for current in numbers[1..] {
   |                    ^^^^^^^^^^^^ the trait `IntoIterator` is not implemented for `[u64]`
   |
   = note: the trait bound `[u64]: IntoIterator` is not satisfied
   = note: required for `[u64]` to implement `IntoIterator`
help: consider borrowing here
   |
19 |     for current in &numbers[1..] {
   |                    +
19 |     for current in &mut numbers[1..] {
   |                    ++++

One solution is to uses borrowing this way :

    let mut first = numbers[0];
    for current in &numbers[1..] {
        first = gcd(first, *current);
    }

but i am not sure to understand how borrowing allow to convert [u64] to iterator ?
and if I understand *current here can be used like mut ?

thanks you for reading.

Do you want to know what what you write doesn't make sense? Or do you want to know how compiler detected the problem?

First of all, for loops require the value being iterated over (i.e. the value after the in keyword) to implement IntoIterator. So if a type doesn't implement IntoIterator, you can't iterate over it.

[T] does not implement IntoIterator. So you can't iterate over it. Simple as that.

Now, &[T] and &mut [T] do implement IntoIterator, so you can iterate over those.

But okay, why is that the case? IntoIterator looks like this:

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;

    fn into_iter(self) -> Self::IntoIter;
}

Ignore the type stuff and just concentrate on into_iter. Note that it takes the receiver self by value. This is why [T] cannot implement IntoIterator. [T] is a "dynamically sized type": the compiler does not (and cannot) know how big values of that type are. As a result, you cannot have variables of these types, you can only refer to them through a pointer.

This is, incidentally, what that "the size for values of type [u64] cannot be known at compilation time" error is talking about.

On the other hand, implementing IntoIterator for &[T] means that self is of type &[T] which is fine.

That's why you have to borrow a [u64] in order to iterate over it.

Edit: oops, forgot to address this. No. Because you use &numbers[1..], current has type &u64. If you want to mutate, you need to use &mut numbers[1..], which will make current be a &mut u64.

6 Likes

You are confusing a reference-typed place that is itself mutable with a reference that points to a mutable place. Those are not the same thing. The former allows ypu to change the reference itself (to point to a different place), while the latter allows you to change the pointed place through the reference.

2 Likes

Thanks for all your responses, It is crystal clear now. I think I don't have yet the reflex to see borrowing as a type with its own features.

Have a nice day.