Reference to a Reference

There are two tools I know of that are particularly useful for answering these kinds of questions, of the form "how does the compiler generate code for a given input?"

Pasting your example into the compiler explorer provides the following output: Compiler Explorer

I have annotated the assembly below with the Rust code in comments:

example::main:
    sub     rsp, 104

    // let x= 10;
    mov     dword ptr [rsp + 12], 10
    lea     rax, [rsp + 12]

    // let y = &x ;
    mov     qword ptr [rsp + 16], rax
    lea     rax, [rsp + 16]

    // let z= &y;
    mov     qword ptr [rsp + 24], rax
    lea     rax, [rsp + 24]

    // let z1 = &z;
    mov     qword ptr [rsp + 32], rax
    lea     rax, [rsp + 32]

    // println!("{}", z1);
    mov     qword ptr [rsp + 40], rax
    lea     rax, [rip + <&T as core::fmt::Display>::fmt]
    mov     qword ptr [rsp + 48], rax
    lea     rax, [rip + .L__unnamed_1]
    mov     qword ptr [rsp + 56], rax
    mov     qword ptr [rsp + 64], 2
    mov     qword ptr [rsp + 72], 0
    lea     rax, [rsp + 40]
    mov     qword ptr [rsp + 88], rax
    mov     qword ptr [rsp + 96], 1
    lea     rdi, [rsp + 56]
    call    qword ptr [rip + std::io::stdio::_print@GOTPCREL]

    add     rsp, 104
    ret

As you can see, all variables (x, y, z, z1) are given their own stack locations. And furthermore each variable (except x for obvious reasons) is a pointer to the last. This is the code as you have written it (e.g. it represents the chain of references in your second diagram). The compiler also outputs a formatter which walks the chain:

<&T as core::fmt::Display>::fmt:
    mov     rax, qword ptr [rdi]
    mov     rax, qword ptr [rax]
    mov     rdi, qword ptr [rax]
    jmp     qword ptr [rip + _ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i32$GT$3fmt17hbb27e86dfb8ce1d4E@GOTPCREL]

I don't know if this is actually an issue in practice, since Rust employs Deref coercions in the type system. E.g. when passing &&T to a function or value which is statically guaranteed to be &T, you get &T. You can see in this case, the stack slots for each variable get optimized out. The code is not storing pointers-to-pointers, just a single pointer to a u32.

If you really truly need to write this kind of reference-to-reference code, you could avoid type inference by annotating the types on each of your variables, or at the very least only on the last one in the chain: Compiler Explorer

7 Likes