Rust Conversions: From, Into, TryFrom, FromStr & AsRef
Programs convert things all the time. Text from a form becomes a number. A tuple of three numbers becomes a Color. One error type becomes another. Rust has a small family of standard traits for this, and once you know them, you’ll see them everywhere in real code.
Think of these traits like the adapters you pack for a trip abroad. Each one is made for one job. Some always fit (From). Some might not fit, and tell you so (TryFrom). Some read a label and build the thing (FromStr). And some just let you plug in whatever you have (AsRef). This page walks through each one, slowly. You already met as on the Type Casting page and a first look at From on the Common Traits page, so we’ll build on those.
Why not just use as?
Section titled “Why not just use as?”as is quick, but it has two limits. It only works between simple built-in types like numbers, char and bool. And it never fails: if a value doesn’t fit, it quietly gives you a different number.
fn main() { let items_in_cart: i32 = 300;
// `as` squeezes 300 into a u8 and silently gets it wrong let squeezed = items_in_cart as u8; println!("with as: {squeezed}");
// `try_from` notices the problem and tells you let checked = u8::try_from(items_in_cart); println!("with try_from: {:?}", checked);
// `from` only exists when the conversion can never lose data let widened = i64::from(items_in_cart); println!("with from: {widened}");}Output:
with as: 44with try_from: Err(TryFromIntError(PosOverflow))with from: 300That 44 is the kind of bug that hides for months. The conversion traits fix this in three ways:
- They are honest.
Fromis only implemented when nothing can go wrong. When something can go wrong, you useTryFrom, which returns aResult. - They work for your own types. You can’t write
(255, 0, 0) as Color, but you can implementFrom<(u8, u8, u8)> for Color. - Other code understands them. The
?operator,.parse(), and many library functions are built on these traits.
So a good rule of thumb: keep as for quick, safe number tricks, and reach for the traits everywhere else.
From and Into: conversions that always work
Section titled “From and Into: conversions that always work”From says “I know how to build myself from that other type.” You’ve used it already. Many standard types implement it:
let greeting = String::from("hello"); // &str -> Stringlet big = i64::from(42_i32); // i32 -> i64, always fitslet precise = f64::from(7_u8); // u8 -> f64, always exactlet scores = Vec::from([90, 85, 77]); // array -> Vec<i32>Notice what is missing: there is no i32::from(some_i64). An i64 might not fit in an i32, so Rust doesn’t pretend it always works. We’ll come back to that in the TryFrom section.
Implementing From for your own type
Section titled “Implementing From for your own type”Here a Color can be built from a tuple of red, green and blue values:
#[derive(Debug)]struct Color { red: u8, green: u8, blue: u8,}
impl From<(u8, u8, u8)> for Color { fn from((red, green, blue): (u8, u8, u8)) -> Self { Color { red, green, blue } }}
fn main() { let orange = Color::from((255, 136, 0)); println!("{:?}", orange);
// We never wrote `Into`, but we get it for free: let sky: Color = (135, 206, 235).into(); println!("sky is r={} g={} b={}", sky.red, sky.green, sky.blue);}Output:
Color { red: 255, green: 136, blue: 0 }sky is r=135 g=206 b=235This is the nice part. The standard library says: “if B implements From<A>, then A automatically implements Into<B>.” So one impl From gives you two ways to convert:
Color::from(tuple)reads as “build aColorfrom this.”tuple.into()reads as “turn this into whatever is needed here.”
Why did we write let sky: Color? Because .into() doesn’t say what to turn into. A tuple could, in theory, be converted into many types. The annotation tells Rust which one you mean. If Rust can already see the target type, for example from a function parameter, you don’t need it.
You’ll often see .into() used in function parameters, too. A function like fn new(title: impl Into<String>) accepts both a &str and a String, and calls title.into() inside. Rust knows the target there, so no annotation is needed. You’ll try this pattern yourself in Practice exercise 2.
From in error handling: how ? converts errors
Section titled “From in error handling: how ? converts errors”Here’s a place where From quietly does a lot of work for you. When ? meets an Err, it doesn’t just return it. It first calls From::from on the error, to turn it into the error type your function returns.
So if your function returns your own AppError, and AppError implements From<ParseIntError>, then ? can turn a parsing error into an AppError by itself:
use std::num::ParseIntError;
#[derive(Debug)]enum AppError { MissingEquals, BadNumber(ParseIntError),}
impl From<ParseIntError> for AppError { fn from(e: ParseIntError) -> Self { AppError::BadNumber(e) }}
// Reads a config line like "max_users = 50"fn read_setting(line: &str) -> Result<(String, u32), AppError> { let (key, value) = line.split_once('=').ok_or(AppError::MissingEquals)?; let number = value.trim().parse::<u32>()?; // ParseIntError -> AppError Ok((key.trim().to_string(), number))}
fn main() { println!("{:?}", read_setting("max_users = 50")); println!("{:?}", read_setting("max_users = lots")); println!("{:?}", read_setting("max_users 50"));}Output:
Ok(("max_users", 50))Err(BadNumber(ParseIntError { kind: InvalidDigit }))Err(MissingEquals)Without the impl From, that second ? would not compile, because Rust wouldn’t know how to turn a ParseIntError into an AppError. With it, your function body stays clean. The Custom Errors page goes deeper into designing error types, and the anyhow and thiserror page shows crates that write these From impls for you.
TryFrom and TryInto: conversions that might fail
Section titled “TryFrom and TryInto: conversions that might fail”Some conversions only work sometimes. 300 doesn’t fit in a u8. -5 is not a valid percentage. For these, Rust has TryFrom. It works like From, but returns a Result, so you have to deal with the “no” case. That’s a good thing: the failure can’t sneak past you.
You already saw the built-in version at the top of this page: u8::try_from(300_i32) gives back an Err instead of a wrong number. And just like From comes with Into, TryFrom comes with TryInto, so let small: Result<u8, _> = big.try_into(); works too. TryInto is in the prelude since the 2021 edition, so you don’t need to import anything.
Implementing TryFrom for your own type
Section titled “Implementing TryFrom for your own type”Let’s make a Percentage type that can only hold 0 to 100. If every Percentage is created through try_from, then every Percentage in your program is valid. You check once, at the door, and never again.
#[derive(Debug)]struct Percentage(u8);
#[derive(Debug)]struct OutOfRange(i32);
impl TryFrom<i32> for Percentage { type Error = OutOfRange;
fn try_from(value: i32) -> Result<Self, Self::Error> { if (0..=100).contains(&value) { Ok(Percentage(value as u8)) // safe: we just checked the range } else { Err(OutOfRange(value)) } }}
fn main() { println!("{:?}", Percentage::try_from(85)); println!("{:?}", Percentage::try_from(150));
let battery: Result<Percentage, _> = (-5).try_into(); println!("{:?}", battery);}Output:
Ok(Percentage(85))Err(OutOfRange(150))Err(OutOfRange(-5))The line type Error = OutOfRange; tells Rust what kind of error this conversion returns. You pick it. Here it carries the bad value, so whoever gets the error can show a helpful message.
FromStr and .parse(): building values from text
Section titled “FromStr and .parse(): building values from text”Text is the most common thing you’ll convert. Files, command-line arguments and web forms all give you strings. The FromStr trait describes “how to build this type from a &str”, and .parse() is the friendly way to call it.
When you write "42".parse::<i32>(), Rust looks up i32’s FromStr implementation and runs it. The ::<i32> part (people call it the “turbofish”) tells Rust which type you want. A type annotation on the variable works too.
let age = "42".parse::<i32>(); // Ok(42)let price: Result<f64, _> = "4.99".parse(); // Ok(4.99)let broken = "forty-two".parse::<i32>(); // Err(ParseIntError { kind: InvalidDigit })Each call returns a Result, because text can always be wrong.
Implementing FromStr for your own type
Section titled “Implementing FromStr for your own type”Imagine a picture-framing shop where customers type sizes like "30x40". Let’s teach Rust to read that:
use std::str::FromStr;
#[derive(Debug)]struct Size { width: u32, height: u32,}
#[derive(Debug)]enum SizeError { MissingX, BadNumber,}
impl FromStr for Size { type Err = SizeError;
fn from_str(text: &str) -> Result<Self, Self::Err> { let (w, h) = text.split_once('x').ok_or(SizeError::MissingX)?; let width = w.trim().parse().map_err(|_| SizeError::BadNumber)?; let height = h.trim().parse().map_err(|_| SizeError::BadNumber)?; Ok(Size { width, height }) }}
fn main() { println!("{:?}", "30x40".parse::<Size>());
println!("{:?}", "30 by 40".parse::<Size>()); println!("{:?}", "30xbig".parse::<Size>());}Output:
Ok(Size { width: 30, height: 40 })Err(MissingX)Err(BadNumber)Two small details. First, FromStr names its error type Err, not Error like TryFrom does. It’s an old naming quirk, so don’t worry if you mix them up at first; the compiler will remind you. Second, you need use std::str::FromStr; to implement the trait, but not to call .parse().
.map_err(|_| SizeError::BadNumber) swaps the number-parsing error for our own error. It’s a handy alternative to writing a From impl when you only need it in one place.
AsRef: accept anything that can lend you a view
Section titled “AsRef: accept anything that can lend you a view”The traits so far create a new value. AsRef is different. It doesn’t build anything. It just says “I can give you a cheap borrowed view of myself as that type.”
Why would you want that? Say you write a function that counts words. If it takes &str, a caller with a String has to write &name. If it takes String, a caller with a &str has to allocate a new String. With impl AsRef<str>, all of them just work:
fn word_count(text: impl AsRef<str>) -> usize { text.as_ref().split_whitespace().count()}
fn main() { let note = String::from("buy milk and eggs");
println!("{}", word_count("hello rust world")); // &str println!("{}", word_count(¬e)); // &String println!("{}", word_count(note)); // String}Output:
344Inside the function, text.as_ref() gives you a plain &str, whatever the caller passed in.
AsRef in the standard library: paths
Section titled “AsRef in the standard library: paths”You’ve probably already used AsRef without noticing. The file functions in std::fs look like this:
pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String>That’s why fs::read_to_string("notes.txt"), fs::read_to_string(my_string) and fs::read_to_string(my_path_buf) all work. You can use the same trick in your own code. This example only looks at path names, so it doesn’t touch any real files:
use std::path::{Path, PathBuf};
fn extension_of(path: impl AsRef<Path>) -> String { match path.as_ref().extension() { Some(ext) => ext.to_string_lossy().to_string(), None => String::from("(none)"), }}
fn main() { println!("{}", extension_of("notes.txt")); println!("{}", extension_of(String::from("holiday.jpeg"))); println!("{}", extension_of(PathBuf::from("backups/2024/site.tar.gz"))); println!("{}", extension_of("README"));}Output:
txtjpeggz(none)AsMut is the same idea, but it lends a mutable view. For example, both a Vec and an array can lend themselves as a mutable slice:
fn reset_all(scores: &mut impl AsMut<[u32]>) { for score in scores.as_mut() { *score = 0; }}
// Both of these work:// reset_all(&mut vec![12, 7, 30]);// reset_all(&mut [3, 1, 4]);You’ll use AsMut much less often than AsRef. It’s enough to recognise it when you see it.
Which one do I use?
Section titled “Which one do I use?”When you’re not sure, this table is a good place to start:
| You want to… | Use | Example |
|---|---|---|
| Quickly convert between number types, accepting truncation | as |
x as f64 |
| Convert when it can never fail | From |
i64::from(n), String::from("hi") |
| Same, when the target type is already known | Into |
let s: String = "hi".into(); |
| Convert when it might fail | TryFrom / TryInto |
u8::try_from(300) |
| Build a value from text | FromStr / .parse() |
"42".parse::<i32>() |
| Accept many string-like or path-like inputs cheaply | AsRef |
fn f(p: impl AsRef<Path>) |
| Turn a value into text for people to read | Display → .to_string() |
42.to_string() |
That last row comes for free: implement Display (see Common Traits) and every value of your type gets a .to_string() method.
Common mistakes
Section titled “Common mistakes”Calling .into() when Rust can’t tell the target type.
fn main() { let name = "Ferris".into(); println!("{name}");}error[E0283]: type annotations needed --> src/main.rs:2:9 |2 | let name = "Ferris".into(); | ^^^^ ---- type must be known at this point | = note: the type must implement `From<&str>` = note: required for `&str` to implement `Into<_>`help: consider giving `name` an explicit type |2 | let name: /* Type */ = "Ferris".into(); | ++++++++++++A &str can turn into a String, a Box<str>, a Vec<u8> and more. Rust won’t guess. Add the type: let name: String = "Ferris".into();, or just write String::from("Ferris").
Implementing Into instead of From. It looks like it should be the same thing, so this is an easy trap:
use std::num::ParseIntError;
#[derive(Debug)]struct AppError(String);
impl Into<AppError> for ParseIntError { fn into(self) -> AppError { AppError(self.to_string()) }}
fn read_port(text: &str) -> Result<u16, AppError> { let port = text.parse::<u16>()?; Ok(port)}error[E0277]: `?` couldn't convert the error to `AppError` --> src/main.rs:13:35 |12 | fn read_port(text: &str) -> Result<u16, AppError> { | --------------------- expected `AppError` because of this13 | let port = text.parse::<u16>()?; | --------------^ the trait `From<ParseIntError>` is not implemented for `AppError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` traitThe free gift only goes one way: implementing From gives you Into, but implementing Into does not give you From. And ?, along with lots of library code, asks for From. So always implement From, and let Into come along by itself. (Clippy, Rust’s linter, even warns about this one.)
Practice
Section titled “Practice”1. Reading a clock time
Section titled “1. Reading a clock time”Make a Time struct with hour: u8 and minute: u8. Implement FromStr so "09:45".parse::<Time>() works. Return an error if there’s no :, if a part isn’t a number, or if the hour is above 23 or the minute above 59. Try it on "09:45", "25:00" and "lunch". Hint: split_once(':') gives you the two halves. A simple String is fine as the error type.
Show solution
use std::str::FromStr;
#[derive(Debug)]struct Time { hour: u8, minute: u8,}
impl FromStr for Time { type Err = String;
fn from_str(text: &str) -> Result<Self, Self::Err> { let (h, m) = text.split_once(':').ok_or("missing ':'")?; let hour: u8 = h.parse().map_err(|_| "hour is not a number")?; let minute: u8 = m.parse().map_err(|_| "minute is not a number")?;
if hour > 23 || minute > 59 { return Err(format!("{text} is not a real time")); } Ok(Time { hour, minute }) }}
fn main() { for input in ["09:45", "25:00", "lunch"] { match input.parse::<Time>() { Ok(t) => println!("{input} -> {}h {}m", t.hour, t.minute), Err(e) => println!("{input} -> error: {e}"), } }}Output:
09:45 -> 9h 45m25:00 -> error: 25:00 is not a real timelunch -> error: missing ':'The ? after .ok_or("missing ':'") works because String implements From<&str>. That’s the same From trick from the error-handling section, with a standard type this time.
2. A kitchen timer
Section titled “2. A kitchen timer”Make a Timer struct with minutes: u32 and seconds: u32. Implement From<u32> so a total number of seconds becomes a Timer (150 becomes 2 minutes 30 seconds). Then write fn start(timer: impl Into<Timer>) that prints the time, and call it with 150 and with Timer { minutes: 1, seconds: 5 }. Hint: minutes are total / 60 and the rest is total % 60. Every type already converts into itself, so passing a Timer works too.
Show solution
struct Timer { minutes: u32, seconds: u32,}
impl From<u32> for Timer { fn from(total: u32) -> Self { Timer { minutes: total / 60, seconds: total % 60 } }}
fn start(timer: impl Into<Timer>) { let t = timer.into(); println!("Timer set for {}:{:02}", t.minutes, t.seconds);}
fn main() { start(150_u32); start(Timer { minutes: 1, seconds: 5 });}Output:
Timer set for 2:30Timer set for 1:05We only wrote From<u32>, yet start accepts both a number and a Timer. The number works through our From, and the Timer works because the standard library says every type converts into itself.
asis for quick number casts and can silently lose data. The conversion traits are explicit, safe, and work for your own types.- Implement
Fromfor conversions that always work, and you getIntofor free..into()needs to know its target type. - The
?operator callsFromto convert error types, soimpl From<OtherError> for MyErrorkeeps error handling short. - Use
TryFrom/TryIntowhen a conversion might fail. They return aResult. - Implement
FromStrto make.parse::<YourType>()work on text. - Use
impl AsRef<str>orimpl AsRef<Path>to accept many input types cheaply, without copying.
Quick check
Section titled “Quick check”Quick check
1. You implement From<Celsius> for Fahrenheit. What else do you get automatically?
2. Which call is the safe way to turn an i32 that might be 300 into a u8?
3. What do you implement so that "30x40".parse::<Size>() works?
4. Why does let name = "Ferris".into(); fail to compile?
Score: 0 / 4
Read more
Section titled “Read more”std::convert— standard library docs: the home ofFrom,Into,TryFrom,TryInto,AsRefandAsMut, with a short guide on when to implement each.FromStr— standard library docs: the trait behind.parse(), with a full example of implementing it.AsRef— standard library docs: howAsRefdiffers fromBorrow, and why it’s used for cheap reference conversions.- Conversion — Rust by Example: short runnable chapters on
From/Into,TryFrom/TryInto, and converting to and from strings. - Conversions — Rust API Guidelines: the community advice on which conversion traits your own types should implement.