Brief
A pointer is a general concept for a variable that contains an address in memory. This address refers to, or “points at,” some other data.
The most common kind of pointer in Rust is a reference. References don’t have any special capabilities other than referring to data, and have no overhead.
Smart pointers are data structures that act like a pointer but also have additional metadata and capabilities.
Rust, with its concept of ownership and borrowing, has an additional difference between references and smart pointers: while references only borrow data, in many cases, smart pointers own the data they point to.
String
and Vec<T>
are two examples of smart pointers. Both these types count as smart pointers because they own some memory and allow you to manipulate it. They also have metadata and extra capabilities or guarantees. String
, for example, stores its capacity as metadata and has the extra ability to ensure its data will always be valid UTF-8.
Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the Deref
and Drop
traits. The Deref
trait allows an instance of the smart pointer struct to behave like a reference so you can write your code to work with either references or smart pointers. The Drop
trait allows you to customize the code that’s run when an instance of the smart pointer goes out of scope.
The smart pointer pattern is a general design pattern used frequently in Rust.
Besides references that are simple addresses, Rust also includes two kinds of fat pointers, two-word values carrying the address of some value, along with some further information necessary to put the value to use.
- A reference to a slice
- A trait object
A pointer to an unsized value is always a fat pointer, two words wide: a pointer to a slice also carries the slice’s length, and a trait object also carries a pointer to a vtable of method implementations.
Rust can’t store unsized values in variables or pass them as arguments. You can only deal with them through pointers like &str
or Box<dyn Write>
, which themselves are sized.
Rust is designed to help keep allocations to a minimum.
Values nest by default. The value ((0, 0), (1440, 900))
is stored as four adjacent integers. If you store it in a local variable, you’ve got a local variable four integers wide. Nothing is allocated in the heap.
This is great for memory efficiency, but as a consequence, when a Rust program needs values to point to other values, it must use pointer types explicitly.
References
It’s easiest to get started by thinking of references as Rust’s basic pointer type. At run time, a reference to an i32
is a single machine word holding the address of the i32
, which may be on the stack or in the heap.
The expression &x
produces a reference to x
; in Rust terminology, we say that it borrows a reference to x
. Given a reference r
, the expression *r
refers to the value r
points to.
Rust references come in 2 flavors:
&T
: an immutable, shared reference.&mut T
: a mutable, exclusive reference.
Boxes
Boxes allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
The simplest way to allocate a value in the heap is to use Box::new
.
Boxes don’t have performance overhead, other than storing their data on the heap instead of on the stack. But they don’t have many extra capabilities either. You’ll use them most often in these situations:
- When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
- When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
- When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
Raw Pointers
Rust has the raw pointer types *mut T
and *const T
. Raw pointers really are just like pointers in C++. Using a raw pointer is unsafe, because Rust makes no effort to track what it points to.
Raw pointers may be null, or they may point to memory that has been freed or that now contains a value of a different type.
You may only dereference raw pointers within an unsafe
block. An unsafe
block is Rust’s opt-in mechanism for advanced language features whose safety is up to you.
Trait Objects
A trait object points to both an instance of a type implementing our specified trait and a table used to look up trait methods on that type at runtime.
We create a trait object by specifying some sort of pointer, such as a &
reference or a Box<T>
smart pointer, then the dyn
keyword, and then specifying the relevant trait.
|
|
A dyn
type, the referent of a trait object. A trait object is a pointer to some value that implements a given trait. For example, the types &dyn std::io::Write
and Box<dyn std::io::Write>
are pointers to some value that implements the Write
trait. The referent might be a file or a network socket or some type of your own for which you have implemented Write
. Since the set of types that implement Write
is open-ended, dyn Write
considered as a type is unsized: its values have various sizes.
We can use trait objects in place of a generic or concrete type. Wherever we use a trait object, Rust’s type system will ensure at compile time that any value used in that context will implement the trait object’s trait. Consequently, we don’t need to know all the possible types at compile time. Rust never knows what type of value a trait object points to until run time.
In Rust, we refrain from calling structs and enums “objects” to distinguish them from other languages’ objects. In a struct or enum, the data in the struct fields and the behavior in impl
blocks are separated, whereas in other languages, the data and behavior combined into one concept is often labeled an object. However, trait objects are more like objects in other languages in the sense that they combine data and behavior. But trait objects differ from traditional objects in that we can’t add data to a trait object. Trait objects aren’t as generally useful as objects in other languages: their specific purpose is to allow abstraction across common behavior.
In memory, a trait object is a fat pointer consisting of a pointer to the value, plus a pointer to a table representing that value’s type. Each trait object therefore takes up two machine words.

If the type implements multiple traits, the vtable will contain pointers to all the trait methods. The vtable still takes up one machine word.
Consider the following code:
|
|
Rust automatically converts ordinary references into trait objects when needed. At the point where the conversion happens, Rust knows the referent’s true type (in this case, File
), so it just adds the address of the appropriate vtable, turning the regular pointer into a fat pointer.
This kind of conversion is the only way to create a trait object.
Slices
|
|
