Question
I have seen the term fat pointer used in several Rust discussions, but I am not sure what it means or when Rust uses one. It appears to be twice the size of a normal pointer, and I do not understand why. I have also noticed that it is often mentioned together with trait objects. What exactly is a fat pointer in Rust, and why is it needed?
Short Answer
By the end of this page, you will understand what a fat pointer is in Rust, why it stores more than just a memory address, and where it appears in everyday Rust code. You will see how Rust uses fat pointers for slices, string slices, and trait objects, and how that extra information makes dynamically sized data usable and safe.
Concept
A fat pointer in Rust is a pointer value that contains more than just an address.
A normal pointer is often called a thin pointer. It usually stores only:
- the address of some value in memory
A fat pointer stores:
- the address of the value
- extra metadata needed to use that value correctly
Why Rust needs fat pointers
Some Rust types do not have enough information available from an address alone.
For example:
- A slice like
&[i32]points to a sequence ofi32values, but the address alone does not tell Rust how many elements are in the slice. - A trait object like
&dyn Displaypoints to some value implementing a trait, but the address alone does not tell Rust which methods to call for the concrete type.
So Rust attaches metadata to the pointer.
Common fat pointer cases
1. Slices
A slice such as:
let nums: &[i32] = &[10, 20, 30];
is a fat pointer containing:
- a pointer to the first element
- the length of the slice
This is why Rust can safely allow:
nums.len()
and bounds-checked indexing.
2. String slices
A string slice like &str is also a fat pointer containing:
- a pointer to the first byte
- the length in bytes
3. Trait objects
A trait object such as:
let x: &dyn std::fmt::Display = &42;
is a fat pointer containing:
- a pointer to the actual value
- a pointer to a vtable
The vtable is a table of function pointers and metadata that tells Rust how to use the concrete type through the trait.
Why they are often twice as large
On a 64-bit system, a normal pointer is usually 8 bytes. A fat pointer usually has:
- one pointer-sized field for the data address
- one pointer-sized field for the metadata
So it is often 16 bytes total.
That is why people say a fat pointer is often "twice as large" as a normal pointer.
Important idea: fat pointers are about dynamically sized or dynamically dispatched data
Rust uses fat pointers when the compiler needs runtime metadata to work with something behind a reference or pointer.
This happens mainly with:
- DSTs (dynamically sized types), such as
[T]andstr - trait objects, such as
dyn Trait
These types cannot be fully described by an address alone.
Mental Model
Think of a normal pointer as a street address written on a note.
That is enough if you already know exactly what is at that address and how large it is.
A fat pointer is like a street address plus instructions.
For slices, the instructions say:
- start here
- read
Nitems
For trait objects, the instructions say:
- the value is here
- use this method table to know which functions belong to this concrete type
So a fat pointer is not "fat" because it points to more data. It is "fat" because it carries extra information needed to interpret the data correctly.
Syntax and Examples
Core syntax
Slice fat pointer
let array = [1, 2, 3, 4];
let slice: &[i32] = &array[1..3];
slice is a fat pointer:
- pointer to
array[1] - length
2
String slice fat pointer
let text: &str = "hello";
text is a fat pointer:
- pointer to the UTF-8 bytes
- byte length
5
Trait object fat pointer
use std::fmt::Display;
let value: &dyn Display = &123;
(, value);
Step by Step Execution
Consider this example:
use std::fmt::Display;
fn show(item: &dyn Display) {
println!("{}", item);
}
fn main() {
let number = 42;
show(&number);
}
Here is what happens step by step:
numberis created as ani32value.&numbercreates a reference to thati32.- The function
showexpects&dyn Display, not&i32. - Rust converts
&i32into a trait object reference&dyn Display. - That trait object reference becomes a fat pointer containing:
- the address of
number - a vtable pointer for
i32implementingDisplay
- the address of
Real World Use Cases
Fat pointers appear in many practical Rust programs.
Working with parts of collections
Slices are used constantly for:
- passing part of an array to a function
- returning borrowed views into data
- avoiding copies
Example:
fn sum(values: &[i32]) -> i32 {
values.iter().sum()
}
The function does not need ownership of a Vec<i32>. It only needs a slice.
Handling text efficiently
&str is used everywhere for:
- function parameters
- parsing input
- reading config values
- borrowing text without allocating
Example:
fn greet(name: &str) {
println!("Hello, {name}!");
}
Dynamic dispatch with trait objects
Trait objects are useful when code should accept different concrete types through a shared interface.
Examples:
- GUI components implementing a trait
Real Codebase Usage
In real Rust projects, developers rarely talk about fat pointers directly every day, but they use the types built on them all the time.
Common patterns
Accepting slices instead of concrete containers
fn process(ids: &[u64]) {
for id in ids {
println!("processing {id}");
}
}
This makes APIs flexible because callers can pass:
- arrays
- vectors
- subslices
Accepting &str instead of String
fn is_valid_name(name: &str) -> bool {
!name.trim().is_empty()
}
This avoids unnecessary allocation and works with both string literals and owned strings.
Using trait objects for runtime polymorphism
trait Handler {
fn handle(&, input: &);
}
(handler: & Handler, input: &) {
input.() {
;
}
handler.(input);
}
Common Mistakes
1. Thinking the pointer points to two separate objects
A fat pointer is one pointer value with two pieces of information. It does not mean there are two heap allocations.
For example, &str does not mean:
- one pointer to text
- one separate owned object for the length
The length is metadata stored in the reference value itself.
2. Assuming all references are fat pointers
This is false.
let x = 10;
let a: &i32 = &x; // thin pointer
let b: &[i32] = &[1,2,3]; // fat pointer
Only references to DSTs or trait objects are fat.
3. Confusing slices with arrays
Broken idea:
let arr: [i32] = [1, 2, 3];
This is only valid when the size is known at compile time, such as [i32; 3].
Comparisons
| Concept | Stores | Sized at compile time? | Common examples | Dispatch/style |
|---|---|---|---|---|
| Thin pointer | Address only | Yes | &i32, *const T to sized T | Direct access |
| Fat pointer to slice | Address + length | Pointer is sized, pointee is DST | &[i32], &str | Runtime length metadata |
| Fat pointer to trait object | Address + vtable pointer | Pointer is sized, pointee is DST | &dyn Display, Box<dyn Write> | Dynamic dispatch |
Cheat Sheet
Quick reference
- Thin pointer = address only
- Fat pointer = address + metadata
Common Rust fat pointers
&[T] // pointer + length
&mut [T] // pointer + length
&str // pointer + byte length
&dyn Trait // pointer + vtable
Box<dyn Trait> // box pointer structure for trait object use
Why fat pointers exist
They let Rust work with values where the address alone is not enough.
Dynamically sized types involved
[T]strdyn Trait
These are usually used behind a pointer.
Size intuition on a 64-bit machine
&i32 -> 8 bytes
&[i32] -> 16 bytes
&str -> 16 bytes
& Trait bytes
FAQ
What is the difference between a thin pointer and a fat pointer in Rust?
A thin pointer stores only a memory address. A fat pointer stores a memory address plus metadata, such as a slice length or a trait object vtable pointer.
Is &str a fat pointer in Rust?
Yes. &str stores a pointer to UTF-8 bytes and the length in bytes.
Is &[T] a fat pointer?
Yes. A slice reference stores a pointer to the first element and the number of elements in the slice.
Why does &dyn Trait need a vtable?
Because the concrete type is not known through the trait object alone. The vtable tells Rust which method implementations to call at runtime.
Are fat pointers always 16 bytes?
Not always. They are often two machine words, so on a 64-bit system they are commonly 16 bytes. This depends on the target architecture.
Does a fat pointer mean extra heap allocation?
No. The extra metadata is part of the pointer value itself, not necessarily a separate allocation.
Are Box<dyn Trait> and &dyn Trait both related to fat pointers?
Yes. Both represent trait objects and carry the metadata needed for dynamic dispatch.
When should I care about fat pointers in Rust?
You should care when working with slices, string slices, trait objects, dynamically sized types, API design, and understanding static versus dynamic dispatch.
Mini Project
Description
Build a small Rust program that demonstrates where fat pointers appear in normal code. The program will compare pointer sizes and then use both a slice and a trait object. This helps connect the idea of "extra metadata" with practical Rust types you already use.
Goal
Create a program that prints the sizes of several reference types and uses a slice plus a trait object in working examples.
Requirements
- Print the size of
&i32,&[i32],&str, and&dyn Display. - Create an array and pass a slice of it to a function.
- Create a function that accepts
&dyn Displayand prints the value. - Keep the program simple and runnable as a single
main.rsfile.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Convert an Integer to a String in Rust
Learn the current Rust way to convert integers to strings, why `to_str()` no longer works, and when to use `to_string()` or `format!`.