7

This is my first encounter with Rust, and I am reading the chapter on vectors in the current version of the Rust Book. I do have previous experience with other languages (mostly functional ones, where the following issues are hidden).

Running the following code snippet (from the book) returns 3:

fn main() {
  let v = vec![1, 2, 3, 4, 5];
  let third: &i32 = &v[2];
  println!("{}", third);
}
  1. The first thing that I do not understand is why the third inside the println! macro isn't referenced. I would have expected the above code to print the memory address of the 3rd element of v (as in C and C++), not its content.

Consider now the code (notice the reference this time inside println!):

fn main() {
  let v = vec![1, 2, 3, 4, 5];
  let third: &i32 = &v[2];
  println!("{}", *third);
}
  1. Why does the code code above produce exactly the same output as the one above it, as if the * made no difference?

Finally, let us rewrite the above code snippets eliminating references completely:

fn main() {
  let v = vec![1, 2, 3, 4, 5];
  let third: i32 = v[2];
  println!("{}", third);
}
  1. Why does this last version produce the same output as the previous two? And what type does v[2] really have: is it an &i32 or an i32?

Are all of the above a manifestation of the automatic dereferencing that is only once alluded to in a previous chapter? (If so, then the book should be rewritten, because it is more confusing than clarifying.)

Alex M.
  • 483
  • 11
  • 23
  • 1
    If you want to know the type you can specify `()` as the type and study the error message, i.e. `let third: () = v[2];`. – starblue Dec 08 '18 at 21:14
  • possible dulication of https://stackoverflow.com/questions/27852613/why-does-printing-a-pointer-print-the-same-thing-as-printing-the-dereferenced-po ? – fghj Dec 08 '18 at 21:53

1 Answers1

2

Disclaimer: I'm learning Rust too, so please take this with a grain of salt.

To understand what happens, it might be easier with cargo-expand.

For the code

fn main() {
  let v = vec![1, 2, 3, 4, 5];
  let third: &i32 = &v[2];
  println!("{}", third);
}

we get (I've removed irrelevant codes)

fn main() {
    ...
    let third: ...
    {
        ::io::_print(::std::fmt::Arguments::new_v1_formatted(                            
            ...
            &match (&third,) {                                                           
                (arg0,) => [::std::fmt::ArgumentV1::new(arg0, ::std::fmt::Display::fmt)],
            },
            ...
        ));
    };
}

for the first/last case, and

fn main() {
    ...
    let third: ...
    {
        ::io::_print(::std::fmt::Arguments::new_v1_formatted(                            
            ...
            &match (&*third,) {                                                           
                (arg0,) => [::std::fmt::ArgumentV1::new(arg0, ::std::fmt::Display::fmt)],
            },
            ...
        ));
    };
}

for the second case.

Which, roughly, means that, for {} formatter, the function fmt (of the trait Display) will be called for a reference to third or *third respectively.

Let's apply this logic for

  • second case: third: &i32 then *third: i32, this is where impl Display for i32 applies.

  • first case: third: &i32, this works also because of impl<'_, T> Display for &'_ T (where T is i32)

  • last case: third: i32: is the same as the first case. Moreover, v[2] (which is of type i32) works because impl Index for Vec (note that: let third = v[2] works because impl Copy for i32, i.e copy semantics is applied for = instead of the default move semantics).

Ta Thanh Dinh
  • 638
  • 1
  • 5
  • 12