I am new to Rust and was reading the book, and when I got to A Complex Example of section 4.1 (The Stack and the Heap).
I thought I was doing well, until I reached the sixth table, where f
at address 11
has value 4
. I expected it to have value 9
, since when baz
is called from bar
, it receives e
as an argument, which in the same table is shown to have value 9
.
What am I missing here?
Thanks!
Ok. The chain starts with d
. To write out the binding in full, we have:
// At address 9:
let d: Box<i32> = Box::new(5); /* = 2^30 - 1 */
So d
is at 9, pointing to 230-1.
We than take the address of d
:
// At address 10:
let e: &Box<i32> = &d; /* = 9 */
And then we call baz
and the wheels fall off because of something I don't think the book has explained to that point. See, baz
takes a &i32
, but e
is a &Box<i32>
. In order to make this work, the compiler does something called "deref coercion". Basically, it "unwraps" the Box
to get at the pointer inside of it. If you have &Ptr<X>
, where Ptr
is some kind of pointery thing, and ask for a &X
, the compiler will repeatedly unwrap the layers of pointery-ness until it finds a plain old &X
. So the actual chain goes &Box<i32>
(= 9) → &i32
(= 230-1).
As a result, I think f
should actually be 230-1: it's the same value as the box, not a pointer to the box (which would be 9. And certainly not 4, which I can't even work out how that happened at all.
Looks like a bug in the explanation.
cc @steveklabnik
1 Like
F should be 9
, yes. This is a typo
It is fixed on beta: The Stack and the Heap
@DanielKeep I wasn't even taking Deref
into account here, at all. Hm.
So the example was meant to have 9 but due to deref coercion it would actually be 230-1, like @DanielKeep pointed out?
I believe so, yes, but haven't tried to verify it myself.
Ok, here's a modified version that reveals the horrifying truth!
macro_rules! show {
($ptr:ident) => {
println!("{} at 0x{:016x} → 0x{:016x}",
stringify!($ptr),
(&$ptr) as *const _ as usize,
(&*$ptr) as *const _ as usize
);
};
}
fn bar() {
let d = Box::new(5);
show!(d);
let e = &d;
show!(e);
baz(e);
}
fn baz(f: &i32) {
show!(f);
}
fn main() {
bar();
}
Output:
d at 0x00007fffb35c10a8 → 0x00007fee14c23010
e at 0x00007fffb35c0fa8 → 0x00007fffb35c10a8
f at 0x00007fffb35c0e50 → 0x00007fee14c23010
2 Likes
So, the Deref does kick in then. That's what I'd expect, but it does make for another wrinkle that makes the example hard to follow...
So f
and d
are pointing to the same thing then.
New question: what is the thing f
and d
are pointing to? Is it an i32
or a Box
?
f
is a &i32
, therefore the thing at 0x00007fee14c23010
must be (or at least be indistinguishable from) an i32
.
So that means that d
is pointing to an i32
as well, which means that, at least in this case, at runtime a Box
is just a regular reference to the thing that it contains, and all the magic was done at compile time. It that reasoning correct?
Not quite. Two things:
First, there's no guarantee that 0x00007fee14c23010
is really an i32
. For example, it could be a pointer to the second field of:
struct Alloc {
size: usize,
value: i32,
guard: usize,
}
where the Box
implementation uses those other fields for things, and just moves its internal pointer around as neccesary. It never tells you this, and you can't really observe it, but it might be there.
Secondly, Box
is not just a normal pointer at runtime: when a Box
is destroyed, it has to run code to deallocate the memory it was pointing to, something normal pointers don't do.
That all having been said, by and large a Box<i32>
is more or less indistinguishable from an &i32
in most of the important ways.
Which is a long-winded way of saying "yes, Box<i32>
is a regular pointer... except for the details, maybe." 
1 Like
Sounds like this rabbit hole goes way deep, but I think I get the main points.
Thank you for the explanation!