rustdereference

Why does Rust compiler suggest adding '&' instead of '*' when both work?


I write a simple program in Rust. This program can compile.

use std::cell::{Ref, RefCell};

fn print_number(x: &i32) {
    println!("x is {}", x);
}

fn main() {
    let stack: i32 = 42;
    let reference: &i32 = &stack;
    let refcell: RefCell<&i32> = RefCell::new(reference);
    let wrapped: Ref<'_, &i32> = refcell.borrow();

    print_number(&wrapped);
    print_number(*wrapped);
}

I can tell the reason why both work:

  1. & works, because &wrapped is of type &Ref<&i32>, which can be deref-coerced to &&i32, which can be deref-coerced to &i32.
  2. * works, because *wrapped is equivalent to *Deref::deref(&wrapped). We know Deref::deref(&Ref<&i32>) results to &&i32, so *Deref::deref(&Ref<&i32>) results to *&&i32, i.e., &i32.

It seems that the 2nd approach (with *) is more straight-forward, however the Rust compiler suggests me using the 1st approach (with &). If I add one line:

print_number(wrapped);

This of course can't compile. But I'm interested with the compiler report:

error[E0308]: mismatched types
  --> src/main.rs:16:18
   |
16 |     print_number(wrapped);
   |     ------------ ^^^^^^^ expected `&i32`, found `Ref<'_, &i32>`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected reference `&i32`
                 found struct `Ref<'_, &i32>`
note: function defined here
  --> src/main.rs:3:4
   |
3  | fn print_number(x: &i32) {
   |    ^^^^^^^^^^^^ -------
help: consider borrowing here
   |
16 |     print_number(&wrapped);
   |                  +

For more information about this error, try `rustc --explain E0308`.
error: could not compile `testrust` (bin "testrust") due to 1 previous error

In the report's "help", it suggests me adding a &. I'm wondering why it doesn't suggest me adding a *.


Solution

  • Recommending & fixes the code for any type T in Ref<'_, T>, while * only works if the contained type implements Copy and is a reference. Copy is needed because else you couldn't "move" out of the Ref and a reference because your function requires it's argument to be one. Special casing "T is a shared reference" (that includes is Copy as shared references always are) doesn't seem all that useful if the other, and already necessary to implement for some cases, way of recommending & also works and produces the same assembly.

    In other words recommending * over & here requires additional code (= more work) for no ergonomics or performance gain whatsoever.