rustrangeiterationimmutabilityborrow

Iterate through a sized Range<T> by borrowing it


Iterating through a Range<T> seems to consume the range instance as into_iter function takes the ownership of the range. By looking at the documentation of range, it is clear that the Borrow trait is only implemented for dynamic range objects. Without cloning, would it be possible to iterate through a range while also passing immutable references of the range to other functions?

let numbers = 500..4000;

// ERROR [(E0277)]: the trait `std::iter::Iterator` is not implemented for `&std::ops::Range<i32>`
for n in &numbers {
    println!("{}", n);

    do_something_else(&numbers);
    reuse_range(&numbers);
}

// Surprisingly, there are no errors when it comes to argument type of functions.
fn do_something_else(range: &Range<i32>) { }
fn reuse_range(range: &Range<i32>) { }

As seen above, functions can take a borrow of Range<T: Sized> but the compiler itself does not allow borrowing ranges.

So far I have tried using Box smart pointer but the behaviour is the same. Simply, by_ref() is available, but that would also restrict us from borrowing as immutable since we already would have a mutable borrow of the same object.


Solution

  • First, let's look at the implementations:

    Range implements Iterator. for loops desugar to a call to std::iter::IntoIterator::into_iter, which is implemented for everything which is already an iterator (since you can obviously create an iterator from an iterator -- just return the iterator). Additionally, Iterator is implemented for (and only for) &mut references to existing iterators.

    From this we can deduce the error:

    Hence, you can't iterator over &Ranges, and instead only over &mut Ranges, or Ranges.

    What should be done instead is to either Clone the Range:

    let my_range = 10..40;
    for i in my_range.clone() {
        println!("{:?}", i);
    }
    

    Take a mutable reference to the range (thereby emptying it too):

    let mut my_range = 10..40;
    
    for i in &mut my_range {
        println!("{:?}", i);
    }
    
    assert_eq!(my_range.next(), None);
    

    Or do the more idiomatic action of just building the range each time:

    for i in 10..40 {
        println!("{:?}", i);
    }
    

    Which is extremely cheap to do.


    Additionally, these rules about implementations of the Iterator trait for &mut and not & references apply to all iterators. This allows for us to do something like this:

    let mut my_iter = 0..100;
    
    // Only take first 50 elements.
    for x in (&mut my_iter).take(50) {
        println!("{:?} < 50", x);
    }
    
    for x in my_iter {
        println!("{:?} >= 50", x);
    }
    

    Note that take takes self, however self is &mut Range, so we don't use up the original Range.