How does dereferencing of a Box work? When one dereferences a boxed value one gets the internal value not a reference. Any idea what is happening behind the scenes? Thanks.
Dereferencing a pointer-like value with *foo
syntax always logically gives you an expression denoting the value (more precisely the “place” of the value) behind the indirection; you only get a reference (mutable or immutable) if you take a reference to this expression, e.g. &*foo
or &mut *foo
, or anything that implicitly works by-reference.
This works the same for Box
, too. The difference is that accessing this expression *foo
by value only works for T: Copy
target types in case of foo: &T
/ foo: &mut T
/ foo: Arc<T>
etc… whereas Box<T>
also allows such an access for non-Copy
types T
, in which case, the value is moved out of the box.
This ability of Box
is still a “magical” ability, not reproducable for any other custom type, since it doesn’t function by means of implementing any trait such as Deref
/DerefMut
; mainly because capturing this interface is kinda hard.
A natural misconception is that for foo: Box<T>
, doing *foo
(and moving out of the resulting place) would simply be sugar for calling a fn(Box<T>) -> T
API, consuming the box.[1] This is not the case. The Box<T>
value is still there until the end of its scope; it doesn’t contain a value anymore, but the memory still exists, and you can fill it up again. E.g. see this code
fn replace_box_contents<T>(x: Box<T>, y: T) -> Box<T> {
let mut the_box = x;
// moves old value out
let old_value = *the_box;
// could not access `the_box` here without compilation error
// e.g. `let _reference = &the_box;` would fail here with
// “borrow of moved value: `the_box`”
// move new value back in
*the_box = y;
// can access again, etc…
let _reference = &the_box;
// return it
the_box
}
this ability of Box<T>
works comparably to the ability to move out of (and back into) fields of structs (that don’t implement Drop
):
struct MyStruct<T>(T);
fn replace_struct_contents<T>(x: MyStruct<T>, y: T) -> MyStruct<T> {
let mut the_struct = x;
// moves old value out
let old_value = the_struct.0;
// could not access `the_struct` here without compilation error
// e.g. `let _reference = &the_struct;` would fail here with
// “borrow of partially value: `the_struct`”
// move new value back in
the_struct.0 = y;
// can access again, etc…
let _reference = &the_struct;
// return it
the_struct
}
I prefer that the error speaks of ”partially moved“ in this case; makes it more easy to learn that the_struct
still exists after moving the_struct.0
, whereas the Box
example above makes it harder to learn that the_box
still contains some Box
value and merely its target has been moved. By the way, this also works behind multiple levels of indirection or structs, etc…
struct MyStruct<T>(T);
fn replace_boxed_struct_contents<T>(x: Box<MyStruct<T>>, y: T) -> Box<MyStruct<T>> {
let mut the_boxed_struct = x;
// moves old value out
let old_value = the_boxed_struct.0;
// could not access `the_boxed_struct` here without compilation error
// e.g. `let _reference = &the_boxed_struct;` would fail here with
// “borrow of partially value: `the_boxed_struct`”
// move new value back in
the_boxed_struct.0 = y;
// can access again, etc…
let _reference = &the_boxed_struct;
// return it
the_boxed_struct
}
(Of course this function is just a toy example; for actually replacing a value like this when you have the replacement readily available already, you could just assign to *the_box
directly to replace and drop the old value, or use
std::mem::replace
replace and keep the old value, but (either way) making it work by just mutable-reference access to the place being replaced.)
-
And if this were the case, the support for this operation would be trivially easy to express with a trait. But it isn’t, hence we still have no “
DerefOwned
”-version ofDeref
/DerefMut
that could make whatBox<T>
offers less magical. ↩︎
The ability to move out of a deref'd box is sometimes called DerefMove
(though no such trait actually exists yet). Some hope it will be less magical in the future.
Ah, DerefMove
it was? I called it DerefOwned
in the footnote I had added to my post above.
That's the terminology I'm familiar with, yeah (there's an old RFC somewhere).
(Missed the footnote, sorry - mobile)
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.