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