Those familiar with c++ definitely know shared_ptr
, unique_ptr
, and Rust also has smart pointers Box
, Rc
, Arc
, RefCell
, etc. This article shares the underlying implementation of Box
.
Box<T>
allocates space on the heap, stores the T value, and returns the corresponding pointer. Also Box
implements trait Deref
dereference and Drop
destructor to automatically free space when Box
leaves the scope.
Getting Started Example
Example from the rust book, without the print statement for demonstration purposes.
Assign the variable 0x11223344 to the heap, the so-called boxing, which java students are surely familiar with. Let’s mount docker, and use rust-gdb to see the assembly implementation.
|
|
The key point is two, alloc::alloc::exchange_malloc
allocates memory space on the heap and then stores 0x11223344
to the address of this malloc
At the end of the function, the address is passed to core::ptr::drop_in_place
to be freed, because the compiler knows that the type is alloc::boxed::Box<i32>
, and will drop the corresponding drop function with Box
Looking at this example alone, Box
is not mysterious, it corresponds to the assembly implementation and is no different from a normal pointer, all constraints are compile-time behavior.
Ownership
This example boxes a string, which is not necessary because String
is, broadly speaking, a smart pointer. This example will report an error.
After *x
is dereferenced, it corresponds to String
, and when it is assigned to y, it executes move semantics, and the ownership is gone, so the subsequent println cannot print x.
|
|
You can take an immutable reference to a string to fix it.
Underlying implementation
Above is the definition of Box
, which can be seen as a tuple structure with two generic parameters: T
for an arbitrary type and A
for a memory allocator. In the standard library A
is the Gloal default value. Where T
has a generic constraint ?Sized
, indicating that the type size may or may not be known at compile time.
This is the Drop
implementation, as stated in the source code, implemented by the compiler.
|
|
implements Deref
to define dereference behavior, and DerefMut
to mutable dereferences. So *x
corresponds to the operation *(x.deref())
.
Applicable scenarios
The official website mentions the following three scenarios, essentially Box
is not very different from a normal pointer, so it is not as useful as Rc
, Arc
, RefCell
.
- When the type does not know the size at compile time, but the code scenario also requires confirming the type size.
- When you have a lot of data and need to move ownership, and don’t want to copy the data.
- trait objects, or dyn dynamic distribution is commonly used to store different types in a collection, or parameters specify different types. s The official website mentions an implementation of a linked table.
The above code does not work, and the reason is simple: it is a recursive definition. It doesn’t work with c code either, we usually have to define next type as a pointer.
The solution given by the official website is to turn next into a pointer Box<List>
, which is common sense, nothing to say.