Adding type annotations breaks compilation?


#1

This code doesn’t compile:

use std::io::Read;

fn main() {
    let a1: [_; 4] = [1, 2, 3, 4];
    let mut a2: [_; 4] = [0; 4];

    let mut b1: &[u8] = &a1;
    {
        let mut b2: &mut [u8] = &mut a2;
        while !b2.is_empty() {
            match b1.read(b2) {
                Err(_) => return,
                Ok(n) => {
                    let tmp : &mut [u8] = b2;
                    b2 = &mut tmp[n..];
                    if n == 0 {
                        break;
                    }
                }
            }
        }
    }
    println!("{:?}", a2);
}

It yields: “error: cannot borrow *b2 as immutable because it is also borrowed as mutable”

But if I remove the type annotation on tmp, it compiles and runs just fine. Any idea what’s going on?


#2

This is really weird, but I think I know what’s going on. In Rust, &mut reference can be either reborrowed or moved. In the line:

let tmp: &mut[u8] = b2;

you want b2 to be moved into tmp, not reborrowed (because if tmp was borrowed from b2, assigning slice of it into b2 would remove the old value, which would invalidate the borrow).

Anyway, it seems that without type inference b2 is moved, and with explicit annotation, it’s borrowed. That’s probably because decisions whether to reborrow/move are mixed with the inference engine, and it seems that without annotation, Rust doesn’t know that b2 is a reference yet, so it makes a conservative decision to move (because every type can be moved). With the annotation on though, it seems that the reborrow/move decision is made after the types are known, so the heuristic choose reborrow.

If you want to keep the annotation and still force the move, here’s how to do it:

let tmp: &mut[u8] = {b2};

or

fn force_move<T>(x: T) -> T { x }
let tmp: &mut[u8] = force_move(b2);
If you, for some reason, wanted the annotation-less code not to compile, you can do it also (it may come handy in some other situation, if you want to prevent move) ```rust let tmp = &mut *b2 ```

#3

This doesn’t work for me on either stable or nightly. The code still fails with the same error.


#4

I thought that I’ve tested it, but you’re right, it doesn’t compile. I have no idea why, though. Maybe it’s just a compiler bug?


#5

It passes the reborrow down, i.e. it’s let tmp: &mut [u8] = {&mut *b2}; not let tmp: &mut [u8] = &mut *{b2};.


#6

It looks like this works:

let tmp : &mut [u8] = (move || b2)();

Is that fully equivalent to the un-annotated let? Will the compiler just optimize out the useless function call?