&Vec<i32> vs &[i32] vs [i32]

I am having difficulty understanding why the following two code examples are different. First the one that works:

fn main() {
    let v: Vec<i32> = vec![1, 2, 3, 4, 5, 6];
    let t = &v[2..4];
    for x in t {
        println!("{}", x);
    }
}

Secondly, the one that does not compile:

fn main() {
    let v: Vec<i32> = vec![1, 2, 3, 4, 5, 6];
    let s = &v;
    let t = s[2..4];
    for x in t {
        println!("{}", x);
    }
}
error[E0277]: the size for values of type `[i32]` cannot be known at compilation time
 --> src/main.rs:4:9
  |
4 |     let t = s[2..4];
  |         ^ doesn't have a size known at compile-time

in the second example, s is already an &Vec<i32>. is it not the same as the first example, but with &v divided into two lines instead of one?

the second example is alternatively fixed by adding an additional & to s, like below:

fn main() {
    let v: Vec<i32> = vec![1, 2, 3, 4, 5, 6];
    let s = &v;
    let t = &s[2..4];
    for x in t {
        println!("{}", x);
    }
}

is the issue that in the second example, in the right-hand expression, s is automatically de-referenced before the slice is created and so it evaluates to [i32] not &[i32]? can anyone explain why the second example is different than the first? maybe spreading something out over two lines doesn't guarantee it evaluates to the same type, because its a different order of operations?

playground

1 Like

When you omit the & you no longer have a very usable slice. The expression s[2..4] alone represents a dynamically sized type (DST) since the length of the range is variable. You need the & or &mut to make it into a fixed size reference to a slice. A slice reference can be copied, passed as a parameter, allocated on the stack, etc.

It can be confusing because sometimes the term slice is used for the DST, but usually it means the slice reference.

1 Like

Yes, this is a precedence problem. Your first example does this (note the parentheses):

let v: Vec<i32> = /* ... */
let t = &(v[2..4]);

t is set to a reference to v[2..4], which is the type &[i32].

The second example does this:

let v: Vec<i32> = /* ... */
let t = (&v)[2..4];

t is set to the value of (&v)[2..4], which is the type [i32], which is unsized.

&v is not executed in the first example at all. The first example does &(v[2..4]), while the second does (&v)[2..4].

3 Likes

You misunderstand the precedence here. A simple rule suffices to remember this and similar case: in Rust, all postfix operators have higher precedence than all prefix operators. Therefore, &v[2..4] is not equivalent to (&v)[2..4], but to &(v[2..4]). Therefore, if you want to split let t = &v[2..4] into two lines, the accurate way to do so is not

    let s = &v;
    let t = s[2..4];

but rather

    let s = v[2..4];
    let t = &s;

@jumpnbrownweasel already explained one reason this doesn't compile — you can't put a dynamically-sized value on the stack. But another scenario with the same operators wouldn't compile either, for a different but related reason:

    let v: Vec<String> = vec![String::new(), String::new()];
    let s = v[0];

This too would fail, even though s is of the perfectly good type String, because it is trying to move the String out of the Vec<String>, leaving it with an unfilled gap, which is not allowed. The theme in both these cases is that v[0] and v[2..4] refers to a place in memory — a portion of the contents of the Vec. When you take a reference to a place, that is different than writing the place expression by itself, which moves or copies out of the place (or fails to compile if that is not possible).

So, there is a sense in which &(expr)[] is acting as a single operator — you can't separate its parts and get the same result.

9 Likes

thank you, all three of these comments helped me to understand the issue.

in particular, grateful to learn about "precedence" and that seems like it will give me a clear language for thinking about and resolving future issues. In particular all postfix operators have higher precedence than all prefix operators.

The second example of @kpreid also is helping me see how splitting &(expr)[] into multiple lines could cause an effect (a move) that wouldn't happen if it was just one line.

thanks for the clarity!

1 Like