Question about the chapter “The Stack and the Heap” in Rust book


#1

the complex example is:


fn foo(x: &i32) {
    let y = 10;
    let z = &y;

    baz(z);
    bar(x, z);
}

fn bar(a: &i32, b: &i32) {
    let c = 5;
    let d = Box::new(5);
    let e = &d;

    baz(e);
}

fn baz(f: &i32) {
    let g = 100;
}

fn main() {
    let h = 3;
    let i = Box::new(20);
    let j = &h;

    foo(j);
}

and the final memory table is:

my question is : why f 's value is not →9?


#2

Because the reference gets implicitly dereferenced.
The important part here is

fn bar(a: &i32, b: &i32) {
    let c = 5;
    let d = Box::new(5);
    let e = &d;

    baz(e);
}

fn baz(f: &i32) {
    let g = 100;
}

The question we’re asking is: what are the types of the different variables?

c is of type i32, no surprises there.
d is of type Box<i32>, and as you probably are aware of, a boxed type is just a fancy name for an owned reference (on the heap, but let’s forget that for a second). The variable itself doesn’t contain the value, but instead contains a pointer to that value, while still calling the destructors when it goes out of scope. As it’s essentially a reference to an integer, let’s simply write down the type of d as &i32.
e must then be of type &&i32, because it holds a reference to a reference to an integer.

When the function baz is called, the compiler realises it needs a reference to an integer. However, it receives a reference to a reference to an integer instead. Now, the designers of the language had two options:

  1. Throw an error
  2. Coerce the double reference into a single reference

Apparently they chose the latter.

The confusion probably stems from the fact that e and f have a different type, and the implicit casting that happens as a result.


#3

Thanks for your patient explanation, it’s very helpful to me!


#4

What is the utility of allowing e to have a type of &&i32 which is a double indirection from the referenced object in a language that doesn’t allow any direct operations on pointers other than dereferencing them to the referenced object?

Seems to me that forming that double indirection type should instead be designed to be an error?


#5

Forming the type should not be an error, because generic code benefits from orthogonality; if T is a type, &T is always also a type.

Runtime is a different matter; I’ve argued in the past that the compiler should implicitly dereference &T if T is a type no larger than a pointer and containing no UnsafeCells, so &u8 and u8 would have the same representation. With stable mem::transmute, the reference representation is unfortunately frozen though.


Why not just add classes?
Rust as a High Level Language