Essential Standard Traits

TryFrom and TryInto

The TryFrom<T> and TryInto<T> traits in std::convert define fallible value conversions that consume a source value and return Result<Self, Self::Error>.

Trait Definitions

pub trait TryFrom<T>: Sized {
    type Error;

    fn try_from(value: T) -> Result<Self, Self::Error>;
}

pub trait TryInto<T>: Sized {
    type Error;

    fn try_into(self) -> Result<T, Self::Error>;
}

Just like From and Into, the standard library provides a blanket implementation of TryInto<U> for every type T where U: TryFrom<T>. Implement TryFrom<T> on your target type, and TryInto<U> is provided automatically.

Additionally, any type that implements infallible From<T> automatically implements TryFrom<T, Error = std::convert::Infallible>.

Checked Integer Narrowing with TryFrom

Unlike the as operator, which silently truncates out-of-range integers, standard integer types implement TryFrom for all other integer types and return Err(TryFromIntError) when the value does not fit in the target range:

fn main() {
    let valid: Result<u8, _> = u8::try_from(200_i32);
    let overflow: Result<u8, _> = 300_i32.try_into();
    let negative: Result<u8, _> = (-5_i32).try_into();

    println!("200 -> u8:  {valid:?}");
    println!("300 -> u8:  {overflow:?}");
    println!("-5  -> u8:  {negative:?}");
}
200 -> u8:  Ok(200)
300 -> u8:  Err(TryFromIntError(()))
-5  -> u8:  Err(TryFromIntError(()))

Implementing TryFrom for Domain Validation

Use TryFrom to enforce domain invariants at construction time so invalid states cannot be represented:

#[derive(Debug)]
struct NonZeroPort(u16);

#[derive(Debug)]
enum PortError {
    ZeroNotAllowed,
    OutOfRange(i64),
}

impl TryFrom<i64> for NonZeroPort {
    type Error = PortError;

    fn try_from(raw: i64) -> Result<Self, Self::Error> {
        let port = u16::try_from(raw).map_err(|_| PortError::OutOfRange(raw))?;
        if port == 0 {
            return Err(PortError::ZeroNotAllowed);
        }
        Ok(NonZeroPort(port))
    }
}

fn main() {
    for candidate in [443, 0, 70_000] {
        println!("{candidate:>6} -> {:?}", NonZeroPort::try_from(candidate));
    }
}
   443 -> Ok(NonZeroPort(443))
     0 -> Err(ZeroNotAllowed)
 70000 -> Err(OutOfRange(70000))

From vs. TryFrom

Trait Can Fail? Return Type Typical Use Case
From<T> No Self Widening (u16 to u64), wrapping (&str to String), error wrapping
TryFrom<T> Yes Result<Self, Self::Error> Narrowing (i64 to u8), parsing/validation, converting raw wire values to enums