16.8Conversions: From and Into
Lesson 4.10 introduced as for numeric casts and promised a "blessed" conversion story for later. Later is now. The From and Into traits are how Rust converts one type into another cleanly, and they're everywhere: String::from, the conversions behind .into(), and, surprisingly, the magic that lets the ? operator turn one error type into another. Conversions are just traits, the chapter's refrain one more time.
The From trait
From is a trait for "build a value of this type from a value of that type." You've called it for ages: String::from("hi") is the From<&str> implementation for String. You implement From to teach your type how to be built from another:
struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Fahrenheit {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
fn main() {
let body = Fahrenheit::from(Celsius(37.0));
println!("{}", body.0);
}98.6
impl From<Celsius> for Fahrenheit reads "a Fahrenheit can be made from a Celsius," and the from method does the math. Now Fahrenheit::from(some_celsius) works. This is a real conversion (newtypes from lesson 10.4), unlike as, which only reinterprets numeric bits; From runs whatever logic the conversion needs.
Into comes free
Here's the elegant part: implementing From automatically gives you Into in the other direction, via a blanket implementation in the standard library (the same kind you saw with Display and ToString in lesson 16.6). So once Fahrenheit implements From<Celsius>, you can also write:
fn main() {
let c = Celsius(37.0);
let f: Fahrenheit = c.into(); // Into, free from the From impl
println!("{}", f.0);
}98.6
c.into() converts c into whatever type the context asks for, here Fahrenheit (from the annotation let f: Fahrenheit). From and Into are two views of one conversion: Fahrenheit::from(c) and c.into() do the same thing. The rule of thumb is implement From, call into(): you only ever write the From impl, and you get the convenient .into() for free.
Key insight
From and Into are the standard, composable way to convert between types, and they're paired so you implement one and get both. This is "blessed" because the whole ecosystem agrees on it: a function that wants to accept "anything convertible into a String" takes impl Into<String>, and callers pass a &str, a String, or any type with the right From impl. Conversions become a shared vocabulary rather than a pile of ad-hoc to_x methods.
TryFrom for conversions that can fail
Some conversions can fail: turning an i64 into an i32 might overflow, parsing text might not work. For those, From's fallible sibling TryFrom returns a Result (lesson 12.2) instead of a bare value:
use std::convert::TryFrom;
struct Percentage(u8);
impl TryFrom<i32> for Percentage {
type Error = String;
fn try_from(value: i32) -> Result<Percentage, String> {
if (0..=100).contains(&value) {
Ok(Percentage(value as u8))
} else {
Err(format!("{value} is not a valid percentage"))
}
}
}
fn main() {
println!("{:?}", Percentage::try_from(50).map(|p| p.0)); // Ok(50)
println!("{:?}", Percentage::try_from(150).map(|p| p.0)); // Err(...)
}Ok(50)
Err("150 is not a valid percentage")
TryFrom mirrors From but its method, try_from, returns Result<Self, Self::Error>, so the conversion can reject bad input. Like From/Into, it has a free counterpart, TryInto, so let p: Percentage = 50.try_into()? works too. Use From when a conversion always succeeds, TryFrom when it can fail, exactly the recoverable-error judgment from lesson 12.5.
How ? uses From
Now a connection that ties chapter 12 to this one. Recall the ? operator (lesson 12.3): it returns the error early on failure. What lesson 12 didn't say is that ? converts the error as it propagates, using From. When you write expr? in a function whose error type differs from expr's error type, ? calls From to convert the one into the other automatically:
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Parse(ParseIntError),
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> AppError {
AppError::Parse(e)
}
}
fn parse_config(text: &str) -> Result<i32, AppError> {
let n: i32 = text.parse()?; // ParseIntError converted to AppError by ?
Ok(n)
}
text.parse() produces a ParseIntError, but parse_config returns AppError. The ? bridges the gap by calling AppError::from(parse_error), which our From impl defines. This is why custom error enums (lesson 12.6) work so smoothly with ?: implement From for each underlying error your type wraps, and ? converts them for you, gathering many error types into your one. It's also why Box<dyn Error> accepts any error through ?: there's a From impl that boxes any error.
Quiz time
Question #1
If you implement From<A> for B, what conversion method do you also get for free, and in which direction?
Show solution
You get Into<B> for A automatically (a blanket impl in the standard library), so a value of type A can call .into() to become a B. B::from(a) and a.into() are the same conversion. The rule of thumb: implement From, call .into().
Question #2
When should a conversion use TryFrom instead of From?
Show solution
When the conversion can fail. From is for conversions that always succeed and return the value directly; TryFrom returns a Result, so it can reject invalid input (an out-of-range number, unparseable text). It's the same recoverable-error judgment as lesson 12.5: if failure is a real possibility, hand back a Result.
Question #3
How does the ? operator use From, and why does that matter for custom error types?
Show solution
When ? propagates an error whose type differs from the function's declared error type, it calls From to convert the original error into the function's error type. This means if you implement From<UnderlyingError> for YourError for each underlying error, ? automatically converts them, letting one function gather several different error types into your single custom error enum (lesson 12.6) with no manual conversion code.
So far, traits have powered generic code, where the compiler knows each concrete type at compile time. The next lesson covers the other mode: trait objects (dyn Trait), which let a single collection hold values of different types that share a trait, choosing the right method at runtime.