Question
In Rust, what is the difference between usize and u32?
The documentation describes usize as:
// Operations and constants for pointer-sized unsigned integers.
In many simple cases, replacing usize with u32 seems to work without changing program behavior. Because of that, it is unclear why Rust needs both types when they appear so similar.
Short Answer
By the end of this page, you will understand what usize means in Rust, how it differs from u32, and why both types exist. You will also learn when to use each one, especially for indexing, memory-related values, and data that must keep the same size across platforms.
Concept
usize and u32 are both unsigned integer types in Rust, but they are designed for different jobs.
u32is always a 32-bit unsigned integer.usizeis an unsigned integer whose size matches the platform's pointer size.
That means:
- On a 32-bit system,
usizeis 32 bits. - On a 64-bit system,
usizeis 64 bits.
Why usize exists
usize is used for values that describe things related to memory or indexing, such as:
- array and slice indices
- lengths of collections
- sizes in memory
- offsets into buffers
Rust uses usize for these because a program cannot index or address more memory than the machine's pointer size can represent.
For example:
let items = vec![10, 20, 30];
let i: usize = 1;
println!("{}", items[i]);
Collection indexing expects usize, not u32.
Why u32 still exists
u32 is for values that must be exactly 32 bits, regardless of platform. This is important when:
- reading or writing binary file formats
- working with network protocols
- matching database field sizes
- saving compact numeric data
- needing predictable ranges across machines
Example:
let user_id: u32 = 42;
A user ID stored as u32 means it is always 32 bits on every platform.
Key idea
Use:
usizefor sizes, lengths, and indexingu32for fixed-width numeric data
Even if they sometimes behave the same on your machine, they are not interchangeable by design.
Mental Model
Think of u32 as a standard shipping box with a fixed size no matter where you send it.
Think of usize as a box that changes size depending on the warehouse equipment. If the warehouse uses bigger lifting machines, the box can be bigger too.
In Rust:
u32is fixed-size everywhereusizeadapts to the machine's pointer size
Another way to think about it:
u32answers: How much data do I want this number type to store?usizeanswers: What size number makes sense for addressing/indexing memory on this machine?
So if you are counting bytes in memory or indexing into an array, usize fits naturally. If you are storing a fixed-format value like a file header field, u32 is the better match.
Syntax and Examples
In Rust, you can declare both types like this:
let a: u32 = 100;
let b: usize = 100;
Basic difference
fn main() {
let x: u32 = 5;
let y: usize = 5;
println!("x = {}, y = {}", x, y);
}
Both hold positive integers, but they are different types.
usize for indexing
fn main() {
let numbers = vec![10, 20, 30];
let index: usize = 1;
println!("{}", numbers[index]);
}
This works because Rust collections use usize for indexing.
u32 does not index directly
fn main() {
let numbers = vec![10, 20, 30];
let index: u32 = 1;
// println!("{}", numbers[index]);
// error: expected usize
println!("{}", numbers[index as usize]);
}
You can convert u32 to usize, but the conversion should be intentional.
Size depends on platform
fn main() {
println!("u32 size: {} bytes", std::mem::size_of::<u32>());
println!("usize size: {} bytes", std::mem::size_of::<usize>());
}
Typical output:
- On a 32-bit machine:
u32andusizeare both 4 bytes - On a 64-bit machine:
u32is 4 bytes,usizeis 8 bytes
That is one reason they may appear similar in small examples.
Step by Step Execution
Consider this example:
fn main() {
let data = [100, 200, 300, 400];
let i: usize = 2;
let value = data[i];
println!("{}", value);
}
Step by step:
datais created as an array with 4 elements.iis set to2and its type isusize.data[i]means: get the element at index2.- Rust accepts this because array indexing requires a
usize. - The value at index
2is300. println!prints300.
Now compare with u32:
fn main() {
let data = [100, 200, 300, 400];
let i: u32 = 2;
let value = data[i as usize];
println!("{}", value);
}
Step by step:
iis stored asu32.- Rust cannot use
u32directly as an array index. i as usizeconverts the value to the platform-sized integer type.- After conversion, indexing works.
- The program prints
300.
This shows that usize is the natural type for indexing, while u32 is just a numeric value that may need conversion first.
Real World Use Cases
When to use usize
Indexing collections
let names = vec!["Ava", "Ben", "Cara"];
let idx: usize = 1;
println!("{}", names[idx]);
Working with lengths
let text = "hello";
let len: usize = text.len();
Buffer sizes and memory offsets
let buffer = vec![0u8; 1024];
let bytes: usize = buffer.len();
These values are tied to memory layout or container size.
When to use u32
File formats and protocols
If a binary format says a field is 32 bits, use u32.
let checksum: u32 = 0xDEADBEEF;
IDs with fixed storage
let product_id: u32 = 50123;
Cross-platform data consistency
If a value must have the same width on every machine, u32 is safer than usize.
Why this matters
If you use usize for serialized data, the size may differ across platforms. That can break compatibility. If you use u32 for indexing everywhere, you may need frequent conversions and may not match library APIs.
Real Codebase Usage
In real Rust codebases, developers usually follow a simple rule:
- use
usizeat the boundaries of collections and memory operations - use fixed-width integers like
u32,u64, ori32for stored or transmitted data
Common patterns
1. Collection APIs return usize
let values = vec![1, 2, 3];
let count = values.len(); // usize
This avoids extra conversions when using lengths and indexes together.
2. Convert external numeric data when needed
fn get_item(items: &[String], id: u32) -> Option<&String> {
let index = usize::try_from(id).ok()?;
items.get(index)
}
This is safer than blindly casting with as.
3. Validation before indexing
fn read_at(data: &[u8], index: usize) -> Option<u8> {
data.get(index).copied()
}
This uses usize because indexing is memory-related.
4. Config and protocol values stay fixed-width
struct Header {
version: u32,
flags: u32,
}
This keeps data layout predictable.
5. Guard clauses around conversions
fn load_record(records: &[u8], record_no: u32) -> Option<u8> {
let index = usize::try_from(record_no).ok()?;
records.get(index).copied()
}
This pattern is common when outside input arrives as one integer type but Rust APIs expect another.
Common Mistakes
1. Using u32 for indexing directly
Broken code:
let items = vec![1, 2, 3];
let i: u32 = 1;
println!("{}", items[i]);
Why it fails:
- indexing expects
usize
Fix:
let items = vec![1, 2, 3];
let i: u32 = 1;
println!("{}", items[i as usize]);
Better fix when conversion may fail in a broader context:
let i: u32 = 1;
if let Ok(index) = usize::try_from(i) {
println!("{}", items[index]);
}
2. Assuming usize is always 64-bit
Many learners use a 64-bit computer and assume usize == u64. That is not always true.
- 32-bit target ->
usizeis 32 bits - 64-bit target ->
usizeis 64 bits
Avoid writing code that depends on usize always being large.
3. Using usize in saved or networked data
Broken idea:
struct Message {
length: usize,
}
Why this is risky:
- the size of
usizechanges by platform - serialized data may become incompatible
Better:
struct Message {
length: u32,
}
Use fixed-width integers for file or network formats.
4. Using as carelessly
let big: u64 = 10_000_000_000;
let small: usize = big as usize;
This may silently truncate on smaller targets.
Safer approach:
let big: u64 = 10_000_000_000;
let small = usize::try_from(big);
5. Choosing type by habit instead of meaning
A good question is not "Which type compiles?" but:
- Is this value about indexing or memory size? Use
usize. - Does this value need a guaranteed width? Use
u32or another fixed-width type.
Comparisons
| Type | Size | Platform-dependent | Best used for | Example |
|---|---|---|---|---|
u32 | 32 bits | No | Fixed-format numeric data | file headers, IDs, protocol fields |
usize | 32 or 64 bits | Yes | Indexing, lengths, memory sizes | vec.len(), array indices |
usize vs u64
| Type | Meaning |
|---|---|
usize | Large enough to index memory on this platform |
u64 | Always exactly 64 bits |
Even on a 64-bit machine, usize and u64 are not the same concept.
usize vs isize
| Type | Signed? | Use case |
|---|---|---|
usize | No | lengths, indexes, counts |
isize | Yes | signed pointer-sized values, some low-level operations |
Practical rule
- Choose
usizewhen working with Rust collection APIs. - Choose
u32when data must stay 32-bit everywhere.
Cheat Sheet
u32= always 32-bit unsigned integerusize= unsigned integer with the same size as a pointer- On 32-bit targets:
usizeis 32 bits - On 64-bit targets:
usizeis 64 bits
Use usize for
- array indexing
- slice indexing
len()values- buffer sizes
- memory offsets
Use u32 for
- file formats
- network protocols
- IDs
- binary data with fixed width
- cross-platform stored values
Important syntax
let a: u32 = 10;
let b: usize = 10;
let len: usize = my_vec.len();
let index: u32 = 3;
let value = my_vec[index as usize];
Safer conversion
let index = usize::try_from(some_u32).ok();
Key rule
If the value describes where something is in memory or a collection, use usize.
If the value describes data that should keep a fixed size everywhere, use u32.
FAQ
Why does Rust use usize for indexing?
Because indexing is tied to memory addresses and collection sizes, which naturally match the machine's pointer size.
Is usize always the same as u64?
No. usize is u64 only on 64-bit targets. On 32-bit targets, it is 32 bits.
Can I replace usize with u32 in Rust?
Sometimes in small programs, but not generally. Many APIs expect usize, especially for indexing and lengths.
Why not use usize for everything?
Because usize changes size across platforms. That makes it a poor choice for file formats, network data, or any value that must have a fixed width.
Should I cast u32 to usize with as?
Only when you are sure the value fits. For safer code, prefer usize::try_from(...) when overflow or truncation is possible.
Is usize only for low-level programming?
No. It appears often in everyday Rust code because vectors, slices, strings, and arrays all use usize for lengths and indexing.
Mini Project
Description
Build a small Rust program that stores user scores in a vector and looks up a score by a user-provided numeric ID. This project demonstrates when an external value might be u32, but indexing into a collection still requires usize.
It is practical because real programs often receive IDs or numbers from files, APIs, or user input as fixed-width integers, then use them to access in-memory collections.
Goal
Create a Rust program that accepts a u32 user ID, safely converts it to usize, and returns the matching score from a vector if the index is valid.
Requirements
- Create a vector of at least five scores
- Store the requested user ID as a
u32 - Convert the
u32ID tousizesafely - Use safe lookup with
.get()instead of direct indexing - Print a helpful message if the ID is out of range
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.