Newbie question about vectors vs vector references

Hello, Rust community!

I am new to the language (but not to programming), and first of all, thanks to the documentation people for their great work.

Anyway, I've been working my way through the (online) Rust Book, and I've come across a little mystery in Chapter 8.

The text gives the following example of creating a vector and accessing one of its elements:

let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];

However, I have discovered that this can be done without references. The following code compiles, runs, and both versions produce the same result:

let v = vec![1, 2, 3, 4, 5];

// This works.
let third: &i32 = &v[2];

println!("The third element is: {} <1>", third);

// This works too!
let third: i32 = v[2];

println!("The third element is: {} <2>", third);

So, can someone please explain why both versions work, and why the book doesn't mention that the assignment to 'third' works without references?

Thanks!

1 Like

The first alternative defines third to be a reference to an i32 (e.g., 8 B on a machine with 64-bit addressing) and assigns it an immutable (i.e., read-only use) reference to the third element of the vector v.

The second alternative defines third to be an i32 (4 B) and moves the value that was in v[2] into third. Afterward the vector element v[2] cannot be read.

1 Like

This works because i32 implements Copy, so the values in the Vec are just copied out. Try the following to see what would happen otherwise:


#[derive(Debug)]
struct NoCopy(i32);

fn main() {
    let v = vec![NoCopy(1), NoCopy(2), NoCopy(3), NoCopy(4), NoCopy(5)];
    
    let third: NoCopy = v[2];
    
    println!("The third element is: {:?}", third);
}
4 Likes

Thank you. That makes sense, but it does not appear to be entirely true. I just wrote this code, which compiles and runs:

fn vector_access_repeated() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];

    println!("The third element is: {} <1a>", third);

    let third: i32 = v[2];

    println!("The third element is: {} <2a>", third);

    let third: &i32 = &v[2];

    println!("The third element is: {} <1b>", third);

    let third: i32 = v[2];

    println!("The third element is: {} <2b>", third);
}

According to your explanation, that should not work. Am I misunderstanding something?

What @TomP said is not entirely correct.

It works for any element type that is Copy, since Rust then knows that you can still safely access both the value in the vector and the copy in your new variable.

For other element types, it won't compile at all ("cannot move out of indexed content") since it would otherwise have to invalidate the whole vector (it cannot be "moved partially").

2 Likes

Ah, OK. And this is because struct types do not implement Copy, correct?

1 Like

Thank you! That helps.

You can find a list of things from the standard library that have built-in implementations of Copy here.

Struct types can implement Copy if you want them to (and it's often a good idea if the struct is simple/represents a value type), but there's a few things that have to be taken into consideration when doing so, hence why it's opt-in rather than opt-out.

If you added Copy (and Clone, since all types that implement Copy also have to implement Clone) to the list of derives in @skysch's example, it'd behave like your original code does :slight_smile:

#[derive(Debug, Copy, Clone)]
struct CanCopy(i32);

fn main() {
    let v = vec![CanCopy(1), CanCopy(2), CanCopy(3), CanCopy(4), CanCopy(5)];
    
    let third: CanCopy = v[2];
    
    println!("The third element is: {:?}", third);
}
2 Likes