15

I am going through the "Rust Book" website in order to learn the language for an upcoming job interview. In the chapter on vectors, there are two code samples:

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }
}

and:

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

Now I am wondering, why is it that for the first sample, when we pass the reference to the vector element i into:

println!("{}", i);

but in the sample where we add 50 to each element of the vector, we have to dereference the element with * before we add to the 50?

Why don't/can't we do the following:

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", *i); // why don't we have to dereference before we pass to println!?
    }
}

or:

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        i += 50; // why can't we just add directly to the reference like this?
    }
}

I must have misunderstood what I read, but I thought Rust was able to discern when you need to dereference or not automatically. I guess I don't understand why we need to dereference (or not dereference) in the two samples. The two examples I provided are commented with the specific bits of code I am wondering about.

Herohtar
  • 5,347
  • 4
  • 31
  • 41
Austin Wile
  • 181
  • 1
  • 5
  • Does this answer your question? [What are Rust's exact auto-dereferencing rules?](https://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules) – Herohtar Apr 18 '22 at 20:18
  • Also related: https://stackoverflow.com/questions/29216530/does-rust-automatically-dereference-primitive-type-references – Herohtar Apr 18 '22 at 20:21
  • The linked answers by Herohtar don't seem to address the fact that `println!` auto-dereferences any value you try to format. If you want to print an address you have to convert the reference it into a raw pointer. – rodrigo Apr 18 '22 at 21:15
  • 1
    Yeah I mean those helped a little bit, but I still don't understand completely. That answer was rather complicated for me. I'm still very very beginner level. Maybe I'll know more once I got further into the rust book, but I thought I'd ask so I could clarify in-case these concepts are super important later on in the book. – Austin Wile Apr 18 '22 at 21:29

1 Answers1

6

I think the easiest way to look at this is that the second example is the "normal" one.

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

i is a &mut i32 (only i32 because there's nothing from which to infer any other integer type), so to assign to it you need to dereference to a mut i32.

The println! example is the one doing some "magic". println! will format types whether they're passed by value or by reference. This is very handy, you wouldn't want it to (for example) clone every string you want to print out but then use later in the application.


Edit:

For completeness, this "magic" isn't really magic at all, but nice use of language features. println! (like every other standard macro that does formatting, like panic! and format!) uses the formatting machinery from the standard library. This can work with any type that implements the Display trait (or the Debug trait if you use {:?}). And Display has a blanket impl for all references of things the implement Display (Debug also does this):

impl<'_, T> Display for &'_ T where
    T: Display + ?Sized, 
{ /* ... */ }

So anything you can format with a value, you can also format with a reference.

JMAA
  • 1,730
  • 13
  • 24
  • This is a great explanation. Just for reference (no pun intended lol), is the "magic" deref coercion? This is something I've read about a few times in various places in my recent studies on rust. Is this what that is or is deref coercion something totally different? – Austin Wile Apr 18 '22 at 22:49
  • 1
    @AustinWile not in this case no, as the edit indicates the "magic" is just that `Display` is implemented on all `&T where T: Display` and delegates to the `T` under the cover. So it's an explicitly implemented behaviour, not any sort of language default. – Masklinn Apr 19 '22 at 09:43
  • 1
    @AustinWile no. Deref coercion is [described well here](https://doc.rust-lang.org/book/ch15-02-deref.html#implicit-deref-coercions-with-functions-and-methods), it's about the `Deref` trait and references `&A` being acceptable where `&B` is required. – JMAA Apr 19 '22 at 09:45
  • 1
    Deref coercion is when *compiler itself* adds dereferences implicitly to get types to match e.g. `foo.bar()` will call `Foo::bar(&self)` even if `foo` is an `&&&&Foo`, because when resolving method calls the compiler adds dereferences until it gets a match. That is also why methods of e.g. `str` are accessible directly on e.g. `&String` without having to explicitly dereference (`String` is very much a smart pointer). – Masklinn Apr 19 '22 at 09:45
  • Thanks for the helpful *pointers (no pun intended again, or is it?? :D )! – Austin Wile Apr 20 '22 at 17:23