RustArena

From and Into in Rust: converting between types

How From and Into work in Rust: implementing From for your types, calling .into(), TryFrom for fallible conversions, and From for ? errors.

From and Into are the standard traits for infallible conversions: turning one type into another when it can't fail.

let s = String::from("crab");     // &str -> String
let n = i64::from(42_i32);         // i32 -> i64 (always fits)
let v: Vec<u8> = "hi".into();      // &str -> Vec<u8>

Implement From, get Into for free

You implement From<Source> for Target. The standard library has a blanket impl that then gives you Into<Target> for Source automatically, so never implement Into yourself.

struct Celsius(f64);
struct Fahrenheit(f64);

impl From<Celsius> for Fahrenheit {
    fn from(c: Celsius) -> Self {
        Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
    }
}

fn main() {
    let boiling = Fahrenheit::from(Celsius(100.0));
    let body: Fahrenheit = Celsius(37.0).into();
    println!("{} {:.1}", boiling.0, body.0);
}
212 98.6

.into() needs to know the target type, from a let annotation, a function parameter, or a return type. Without one:

fn main() {
    let x = 5_i32.into();
    println!("{x}");
}
error[E0283]: type annotations needed

Where From matters most: errors and ?

The ? operator converts errors with From::from. If your error type implements From<std::io::Error> and From<ParseIntError>, one function can use ? on both kinds of failure and get your error type out. thiserror's #[from] attribute writes those impls for you; anyhow::Error implements From for every standard error.

Flexible parameters with Into

A constructor that takes impl Into<String> accepts both &str and String, without forcing callers to write .to_string():

fn new(name: impl Into<String>) -> Self {
    Self { name: name.into() }
}

Use this for public APIs where it helps callers; don't use it everywhere, since it makes signatures harder to read and every generic parameter adds compile time.

TryFrom and TryInto: conversions that can fail

When a conversion can fail (an i64 to a u8, a string to an email address, a raw number to an enum), implement TryFrom instead. It returns a Result and has an associated Error type:

impl TryFrom<u8> for Level {
    type Error = String;
    fn try_from(n: u8) -> Result<Self, Self::Error> { ... }
}

You then get try_into() for free, just like into().

Related traits

Common mistakes

Example

use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq)]
enum Level {
    Info,
    Warn,
    Critical,
}

impl TryFrom<u8> for Level {
    type Error = String;

    fn try_from(n: u8) -> Result<Self, Self::Error> {
        match n {
            0 => Ok(Level::Info),
            1 => Ok(Level::Warn),
            2 => Ok(Level::Critical),
            other => Err(format!("no level {other}")),
        }
    }
}

struct LogLine {
    level: Level,
    message: String,
}

impl LogLine {
    fn new(level: Level, message: impl Into<String>) -> Self {
        Self { level, message: message.into() }
    }
}

impl From<LogLine> for String {
    fn from(line: LogLine) -> Self {
        format!("[{:?}] {}", line.level, line.message)
    }
}

impl fmt::Display for Level {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", format!("{self:?}").to_uppercase())
    }
}

fn main() {
    let a = LogLine::new(Level::Info, "server started");
    let b = LogLine::new(Level::Warn, String::from("disk at 91%"));
    let rendered: Vec<String> = vec![a.into(), String::from(b)];
    println!("{rendered:?}");

    for raw in [1_u8, 2, 9] {
        let parsed: Result<Level, _> = raw.try_into();
        match parsed {
            Ok(level) => println!("{raw} -> {level}"),
            Err(e) => println!("{raw} -> error: {e}"),
        }
    }

    let big: i64 = 300_i32.into();
    println!("{big} {:?}", u8::try_from(big));
}

Output:

["[Info] server started", "[Warn] disk at 91%"]
1 -> WARN
2 -> CRITICAL
9 -> error: no level 9
300 Err(TryFromIntError(PosOverflow))

Practice it