Skip to content

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.

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: 44
with try_from: Err(TryFromIntError(PosOverflow))
with from: 300
▶ Try it Yourself

That 44 is the kind of bug that hides for months. The conversion traits fix this in three ways:

  • They are honest. From is only implemented when nothing can go wrong. When something can go wrong, you use TryFrom, which returns a Result.
  • They work for your own types. You can’t write (255, 0, 0) as Color, but you can implement From<(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 -> String
let big = i64::from(42_i32); // i32 -> i64, always fits
let precise = f64::from(7_u8); // u8 -> f64, always exact
let 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.

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=235
▶ Try it Yourself

This 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 a Color from 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)
▶ Try it Yourself

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.

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))
▶ Try it Yourself

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.

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)
▶ Try it Yourself

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(&note)); // &String
println!("{}", word_count(note)); // String
}

Output:

3
4
4
▶ Try it Yourself

Inside the function, text.as_ref() gives you a plain &str, whatever the caller passed in.

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:

txt
jpeg
gz
(none)
▶ Try it Yourself

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.

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.

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 this
13 | 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` trait

The 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.)

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 45m
25:00 -> error: 25:00 is not a real time
lunch -> error: missing ':'
▶ Try it Yourself

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.

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:30
Timer set for 1:05
▶ Try it Yourself

We 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.

  • as is for quick number casts and can silently lose data. The conversion traits are explicit, safe, and work for your own types.
  • Implement From for conversions that always work, and you get Into for free. .into() needs to know its target type.
  • The ? operator calls From to convert error types, so impl From<OtherError> for MyError keeps error handling short.
  • Use TryFrom / TryInto when a conversion might fail. They return a Result.
  • Implement FromStr to make .parse::<YourType>() work on text.
  • Use impl AsRef<str> or impl AsRef<Path> to accept many input types cheaply, without copying.

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