0

I'm writing some code in Rust for generating the first 500 numbers in the Collatz sequence of 46445645645564584.

use std::fmt::Debug;

//Calculate the first 500 numbers in the Collatz sequence of 46445645645564584

fn main() {
    let mut v = vec![46445645645564584];

    for _ in 0..500 {
        let last = v[v.len() - 1];
        v.push(next(last));
    }

    print_array(&v);
}

fn next(n: i64) -> i64 {
    if n % 2 == 0 {
        n / 2
    } else {
        3 * n + 1
    }
}

fn print_array<T: Debug>(v: &[T]) {
    for x in v {
        println!("{:?}", x);
    }
}

This works, but I want to inline the variable last:

for _ in 0..500 {
    v.push(next(v[v.len() - 1]));
}

In my eyes, this should not change the semantics of the program, since I've simply inlined a variable. However, the Rust compiler gives the following error when I try to compile this:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:9:21
  |
9 |         v.push(next(v[v.len() - 1]));
  |         -           ^              - mutable borrow ends here
  |         |           |
  |         |           immutable borrow occurs here
  |         mutable borrow occurs here

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:9:23
  |
9 |         v.push(next(v[v.len() - 1]));
  |         -             ^            - mutable borrow ends here
  |         |             |
  |         |             immutable borrow occurs here
  |         mutable borrow occurs here

As I see it, the immutable borrow of v should be dropped right when the value v[v.len() - 1] is computed and passed to next(). That means the mutable borrow of v would be successful in the outermost call to v.push(), since the previous borrow was dropped. Am I seeing this wrong? Or is this a compiler bug?

I am aware you could also do this with iterators/generators, but I'd like to know why this is happening in this specific piece of code, since this might be a problem I'll run into again at some point.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Bauxite
  • 3
  • 2

1 Answers1

0

While you are right that it would be possible in principle to extend the power of Rust's borrow checker to be able to do the analysis you've done, the borrow checker isn't currently capable of that kind of reasoning.

As you can see in the error you are given, v.push borrows v, and after that, the expression next(v[v.len() - 1]) is analyzed; but v is already borrowed by v.push, so the borrow checker fails.

The borrow checker can be finicky, and obviously it can be improved, but as of now this is the expected behavior for the borrow checker, and the solution is to just separate out the indexing operation from the pushing operation, as you originally did. The compiler is smart enough to inline the operation (after it has made all of the borrow checks) anyway.

Izaak Weiss
  • 1,281
  • 9
  • 18
  • The problem should actually be fixed as soon as [non-lexical lifetimes](http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/) make it into the stable compiler. – user4815162342 Nov 23 '17 at 18:51