Question
I want to convert between numeric types in Rust safely and idiomatically.
For example, how should I convert a usize to a u32?
A direct cast like this works:
let x: usize = 4_294_967_295;
let y = x as u32;
But a cast can silently truncate or overflow:
let x: usize = 4_294_967_296;
let y = x as u32; // becomes 0
Older Rust discussions mention traits such as ToPrimitive and FromPrimitive, which return values like Option<u32>, but those APIs were unstable at the time.
So what is the modern, idiomatic, and safe way to convert between numeric types and pointer-sized types in Rust?
I am especially interested in cases involving usize and isize, since their size depends on the platform. For example, if I store tree indices in a Vec<u32> and later use them for indexing:
let t = vec![0u32, 0u32, 1u32];
let grandparent = t[t[2] as usize];
how should this kind of conversion be handled safely, and what happens on platforms where usize is smaller than u32?
Short Answer
By the end of this page, you will understand when to use as, From, Into, TryFrom, and TryInto for numeric conversions in Rust. You will also learn why pointer-sized integers like usize need special care, how to avoid silent truncation, and how to write conversions that are safe across platforms.
Concept
Rust has multiple ways to convert numeric types, and each one has a different safety story.
The core idea is this:
- Use
From/Intowhen the conversion is guaranteed to succeed. - Use
TryFrom/TryIntowhen the conversion might fail. - Use
aswhen you intentionally want Rust's casting behavior, including truncation or reinterpretation rules.
This matters because not all numeric conversions are equally safe.
For example:
u8tou32is always safe because everyu8value fits inu32.u32tou8is not always safe because manyu32values are too large foru8.u32tousizedepends on the platform, becauseusizeis 32-bit on some systems and 64-bit on others.
In modern Rust, the idiomatic approach is to make that risk explicit.
Mental Model
Think of each integer type as a container with a fixed capacity.
- A
u8is a tiny box. - A
u32is a bigger box. - A
usizeis a box whose size depends on the machine.
Now imagine pouring water from one box into another:
- If you pour from a small box into a bigger box, everything fits.
- If you pour from a big box into a smaller box, some water may spill.
Fromis used when Rust knows nothing can spill.TryFromis used when Rust says this might spill, so check first.asis like saying pour it anyway.
For usize, the box changes size depending on the computer. That is why conversions involving usize should be treated carefully in portable code.
Syntax and Examples
Core conversion tools in Rust
1. From for guaranteed-safe conversions
let x: u8 = 42;
let y: u32 = u32::from(x);
Use this when the destination type can always represent the source value.
2. TryFrom for checked conversions
use std::convert::TryFrom;
let x: usize = 42;
let y: Result<u32, _> = u32::try_from(x);
This is the idiomatic choice when the conversion might fail.
3. Into and TryInto
These are often used when type inference reads better from left to right.
use std::convert::TryInto;
: = ;
: <, _> = x.();
Step by Step Execution
Consider this example:
use std::convert::TryFrom;
fn main() {
let raw: usize = 500;
let converted = u8::try_from(raw);
match converted {
Ok(value) => println!("Converted: {}", value),
Err(_) => println!("Value does not fit in u8"),
}
}
Step by step
rawis created with the value500.- Rust tries to convert
500fromusizeintou8. - A
u8can only store values from0to255. - Since
500is too large, the conversion fails. u8::try_from(raw)returns .
Real World Use Cases
Indexing collections
Rust collections like Vec, slices, and strings use usize for indexing.
use std::convert::TryFrom;
fn get_item(items: &[String], id: u32) -> Option<&String> {
let index = usize::try_from(id).ok()?;
items.get(index)
}
This is common when IDs come from files, APIs, or databases.
Parsing external data
You may read a large integer from JSON, a config file, or user input and need to store it in a smaller type.
use std::convert::TryFrom;
fn parse_port(value: u32) -> Result<u16, String> {
u16::try_from(value).map_err(|_| "port out of range".to_string())
}
Interfacing with binary formats
Real Codebase Usage
In real Rust codebases, developers usually follow a few practical patterns.
Prefer the right type from the beginning
If a value is used for indexing, store it as usize when possible.
struct Node {
parent: usize,
}
This avoids repeated conversions.
Convert at boundaries
If data enters your system as u32, convert it once when parsing or validating.
use std::convert::TryFrom;
fn parse_index(raw: u32) -> Result<usize, String> {
usize::try_from(raw).map_err(|_| "index does not fit into usize".to_string())
}
This keeps the rest of the program simpler.
Use guard clauses
use std::convert::TryFrom;
fn get_parent(tree: &[u32], node: usize) <> {
= *tree.(node)?;
= ::(parent_index_u32).()?;
tree.(parent_index).()
}
Common Mistakes
1. Using as when you actually need validation
Broken example:
let big: u32 = 1000;
let small = big as u8;
This compiles, but small becomes 232, not an error.
Better
use std::convert::TryFrom;
let big: u32 = 1000;
let small = u8::try_from(big);
assert!(small.is_err());
2. Assuming usize is always u64
Broken assumption:
let x: u32 = 10;
let : = x ;
Comparisons
| Conversion tool | Fails at runtime? | Checked for fit? | Typical use |
|---|---|---|---|
as | No | No | Intentional cast, truncation, low-level conversions |
From | No | Yes, by type design | Widening or guaranteed-safe conversions |
Into | No | Yes, via From | Same as From, often nicer with inference |
TryFrom | Yes, returns Err | Yes | Narrowing or platform-dependent conversions |
Cheat Sheet
// Guaranteed-safe conversion
let x: u8 = 5;
let y: u32 = u32::from(x);
// Checked conversion
use std::convert::TryFrom;
let n: usize = 100;
let m: u32 = u32::try_from(n)?;
// Equivalent style
use std::convert::TryInto;
let m: u32 = n.try_into()?;
// Unchecked cast
let a: u32 = 300;
let b: u8 = a as u8; // 44
Rules of thumb
- Use
Fromfor always-safe conversions. - Use
TryFromfor possibly failing conversions. - Use
asonly when cast behavior is intentional.
FAQ
How do I safely convert usize to u32 in Rust?
Use u32::try_from(value). It returns a Result, so you must handle the case where the value is too large.
Should I use as for numeric conversions in Rust?
Only when truncation or cast semantics are intentional. For correctness-sensitive code, prefer From or TryFrom.
What is the difference between From and TryFrom in Rust?
From is for conversions that always succeed. TryFrom is for conversions that may fail because the destination type cannot represent all source values.
Why does Rust use usize for indexing?
usize matches the platform's pointer size, which makes it the correct type for indexing memory-backed collections like Vec and slices.
Can converting u32 to usize fail?
Mini Project
Description
Build a small Rust program that reads parent indices from a vector and safely finds a node's grandparent. This demonstrates checked numeric conversion from u32 to usize and safe vector access without panics.
Goal
Write a function that returns a node's grandparent from a tree stored as Vec<u32>, using checked conversion and bounds-safe indexing.
Requirements
- Store the tree as a
Vec<u32>where each value represents a parent index. - Write a function that takes the tree and a node index.
- Convert
u32parent indices tousizesafely. - Avoid panics by using safe indexing.
- Return
Option<u32>when the grandparent may not exist.
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!`.