From and Into in Rust: vs TryFrom/TryInto Guide

Max WellsMax WellsFounder of Rustify

TL;DR: From<T> and Into<T> are Rust's standard traits for infallible type conversion. Implement From<T> for U and you get Into<U> for T automatically, you only ever implement From. Use Into<T> in function signatures to accept multiple input types. The ? operator uses From to convert errors; String::from("hello") and "hello".into() are the same call. For fallible conversions, use TryFrom / TryInto instead.


What Are From and Into?

From<T> defines how to construct a type from another type. Into<T> is the mirror, implementing From<A> for B automatically gives you Into<B> for A.

// From: construct a String from a &str
let s = String::from("hello");
 
// Into: equivalent call; compiler infers the target type
let s: String = "hello".into();
 
// Both call the same code; Into is auto-derived from From

You only ever write impl From<T> for U, never impl Into<U> for T. The standard library provides the blanket impl:

// This is in std; you get it for free
impl<T, U> Into<U> for T where U: From<T> {
    fn into(self) -> U {
        U::from(self)
    }
}

How Do You Implement From?

Implement From<OtherType> for your type to enable seamless conversions.

#[derive(Debug)]
struct Celsius(f64);
 
#[derive(Debug)]
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 = Celsius(100.0);
    let f = Fahrenheit::from(boiling);     // explicit
    println!("{:?}", f);                    // Fahrenheit(212.0)
 
    let freezing = Celsius(0.0);
    let f: Fahrenheit = freezing.into();   // via Into
    println!("{:?}", f);                    // Fahrenheit(32.0)
}

How Do You Use Into<T> in Function Signatures?

Accept impl Into<T> in function parameters to take any type that can be converted, callers pass values without calling .into() manually.

struct Config {
    host: String,
    port: u16,
}
 
impl Config {
    fn new(host: impl Into<String>, port: u16) -> Self {
        Config { host: host.into(), port }
    }
}
 
fn main() {
    // All three work; no .to_string() or String::from() needed
    let c1 = Config::new("localhost", 8080);           // &str
    let c2 = Config::new(String::from("example.com"), 443);  // String
    let s = "api.server.com".to_string();
    let c3 = Config::new(s, 3000);                     // owned String
}

This is the idiomatic Rust pattern for functions that store strings, accept impl Into<String> instead of &str or String.


How Does From Power the ? Operator?

When using ? to propagate errors, Rust calls From::from(err) to convert the error type. This is how #[from] in thiserror works.

use std::num::ParseIntError;
use std::io;
 
#[derive(Debug)]
enum AppError {
    Parse(ParseIntError),
    Io(io::Error),
}
 
impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
}
 
impl From<io::Error> for AppError {
    fn from(e: io::Error) -> Self { AppError::Io(e) }
}
 
fn parse_port(s: &str) -> Result<u16, AppError> {
    let port = s.parse::<u16>()?;  // ParseIntError → AppError via From
    Ok(port)
}

The ? operator desugars to roughly: return Err(From::from(err)).


What Is the Difference Between From/Into and TryFrom/TryInto?

From/Into are infallible, the conversion always succeeds. TryFrom/TryInto return Result<T, E> for conversions that can fail.

// Infallible; i32 always fits in i64
let x: i64 = i64::from(42i32);
 
// Fallible; i32 might not fit in u8
let y: Result<u8, _> = u8::try_from(300i32);  // Err; 300 > 255
let z: Result<u8, _> = u8::try_from(200i32);  // Ok(200)
TraitFallible?Returns
From<T>NoU
Into<T>NoT
TryFrom<T>YesResult<U, E>
TryInto<T>YesResult<T, E>

Use TryFrom/TryInto when the conversion can logically fail (overflow, invalid input). Use From/Into only when it is guaranteed to succeed.


Frequently Asked Questions

&str forces the caller to pass a string slice, they can't pass an owned String without borrowing. impl Into<String> accepts both. Inside the function, call .into() once to get the owned String. If you're storing the value, this is cleaner than s.to_string() at every call site.

Yes. You can have as many From implementations as needed, provided the source types differ.

The conversion you need doesn't exist. Either implement From<X> for Y yourself, or convert manually.

Yes, if the compiler can't infer the target type, you get "type annotations needed". Fix it with an explicit type: let x: TargetType = value.into() or use TargetType::from(value).


Sources


  • Trait: From and Into are standard library traits
  • Try Operator: ? relies on From for error conversion
  • thiserror: Uses #[from] to generate From impls automatically
  • Generic: impl Into<T> uses generics in function parameters

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?