From and Into

How to use Rust's From and Into traits to convert between types, why both exist, and how to implement them in practice.

Conversions between types are everywhere in Rust. You turn a string slice into an owned String, an integer into a floating-point number, or a custom error into a boxed trait object. The From and Into traits in std::convert provide the idiomatic, infallible way to perform these ownership-taking conversions. Once you understand their one-way, consume-and-produce model and the automatic link between them, you can write generic functions that accept anything convertible into what you need.

The Core Idea

From and Into express a single direction of conversion: A → B. They consume the input value, produce an output value, and never fail. The two traits are two sides of the same coin. From<A> for B lets you call B::from(a). The reciprocal trait Into<B> for A lets you call a.into(). When you implement From, Rust provides Into automatically. The separation exists so generic code can be written naturally whether the caller thinks in terms of “producing a B from an A” or “turning an A into a B”.

Infallible Conversions Only:

If your conversion might fail — parsing a string to a number, for example — use TryFrom and TryInto, not From or Into. The from and into methods have no Result return; they must succeed for all possible inputs of the source type.

The From Trait: Defining Custom Conversions

From is the trait you implement. It has one required method:

pub trait From<T>: Sized {
    fn from(value: T) -> Self;
}

The method takes ownership of value and returns Self. Implementing From tells the compiler “my type can be built from a T.”

A basic example: a Person struct that can be created from a &str containing a name.

struct Person {
    name: String,
}
impl From<&str> for Person {
    fn from(s: &str) -> Self {
        Person {
            name: s.to_owned(),
        }
    }
}

Now you can write Person::from("Alice") and get a Person. The conversion takes the string slice, copies it into a String, and moves the ownership into the struct. No intermediate borrowing or potential failure — it always works.

From Does Not Give the Reverse Direction:

Implementing From<&str> for Person creates a &str → Person conversion. It does not let you turn a Person back into a &str. That would require a separate impl From<Person> for &str, which is often impossible because &str is a reference that would need to borrow from inside the Person. Convert with care about lifetimes.

The Into Trait: The Reverse Direction for Free

The Into trait is what you call on a value when you want to turn it into something else:

pub trait Into<T>: Sized {
    fn into(self) -> T;
}

You do not implement Into directly. The standard library provides a blanket implementation:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

This reads: “If there is a From<T> for U, then any T can become a U via into().” In our Person example, the blanket impl means you can write:

let p: Person = "Alice".into();

into() calls Person::from("Alice") under the hood. The conversion direction is still &str → Person — you have just written it from the side of the source value, not the target type.

You Get Into Automatically:

Once you've written impl From<Source> for YourType, test that .into() works: let y: YourType = source.into();. If that compiles, your blanket impl is active and working correctly.

Why Both Traits Exist

A common first reaction is: why have two traits that do the same conversion? They are not two separate directions — both represent A → B, but they put the emphasis on different sides.

  • From is the constructor perspective: “Build me a U from this T.”
  • Into is the consumer perspective: “Take this value and turn it into whatever you need.”

In generic code, requiring Into<T> as a parameter bound is often more ergonomic. You can accept impl Into<T> and let the caller pass values that can be turned into T, without the caller ever naming the source type. Consider accepting any address-like type that can become an Ipv4Addr:

use std::net::Ipv4Addr;
fn ping<A>(address: A) -> std::io::Result<bool>
where
    A: Into<Ipv4Addr>,
{
    let ipv4_address = address.into();
    // ... use ipv4_address
}

A caller can pass an Ipv4Addr directly, or a [u8; 4], or a u32 — all of which implement Into<Ipv4Addr>. If the function required From<A> for Ipv4Addr directly, it would be less natural to write because the caller would need to specify the generic A explicitly. Into lets Rust infer it from the value.

Using From and Into in Function Signatures

When writing a function that needs a String, accept impl Into<String> instead of &str. This lets callers pass a &str, a String, or a Box<str> — anything that can be turned into a String.

fn greet(name: impl Into<String>) {
    let name: String = name.into();
    println!("Hello, {name}!");
}
greet("World");        // &str -> String
greet(String::from("Ferris")); // String -> String (identity)

The blanket impl From<T> for T (the identity conversion) means that passing a String to greet works without extra effort. The function remains generic but readable.

Accepting impl Into<T> is the Rusty pattern for flexible input parameters — it costs nothing at runtime (the conversion is monomorphized) and makes the caller's code cleaner.

Don't Confuse Into With Dual-Direction Conversion:

Into<U> for T does not create a conversion back from U to T. Many newcomers expect that implementing From<A> for B somehow opens a path from B to A. It does not. Each direction requires its own From implementation.

Implementing From Step by Step

Suppose you have a custom Temperature type and you want to create it from both f64 (Celsius) and (f64, &str) (value with unit). Here is the complete process.

1

Step 1: Define Your Type

Create a struct that will hold the converted data. Nothing special is required for From.

struct Temperature {
    celsius: f64,
}
2

Step 2: Implement From for the First Source Type

Write an impl block for From<f64> for Temperature. The from method must construct a Temperature from the f64 value.

impl From<f64> for Temperature {
    fn from(c: f64) -> Self {
        Temperature { celsius: c }
    }
}
3

Step 3: Implement From for a Second Source Type (Optional)

You can add as many From implementations as you need. Here we convert a tuple of value and unit.

impl From<(f64, &str)> for Temperature {
    fn from((value, unit): (f64, &str)) -> Self {
        match unit {
            "C" | "c" => Temperature { celsius: value },
            "F" | "f" => Temperature { celsius: (value - 32.0) * 5.0 / 9.0 },
            _ => panic!("Unknown unit: {}", unit),
        }
    }
}

Infallibility Means No Error Return:

This implementation panics on an unknown unit. In production, you would use TryFrom and return a Result instead, but From demands infallibility. If the conversion can fail for some input, From is the wrong tool.

4

Step 4: Use `.into()` to Test

Thanks to the blanket Into impl, you can now write both Temperature::from(36.6) and 36.6.into(). Test it:

fn main() {
    let t1: Temperature = 22.5.into();
    let t2: Temperature = (212.0, "F").into();
    println!("{:.1}°C and {:.1}°C", t1.celsius, t2.celsius);
}

This works because the compiler sees 22.5: f64 and (212.0, "F"): (f64, &str) and calls Temperature::from for each.

Common Mistakes and Misconceptions

Thinking Into Gives You a Round-Trip
Implementing From<A> for B creates A → B. It does not create B → A. If you need both directions, you must implement From<B> for A separately, if it's possible.

Using Into Where From Is Simpler
When you know the target type, From can be clearer: Person::from("Alice") states intent more explicitly than let p: Person = "Alice".into(), which relies on type inference. Both are fine; choose the one that makes the code most readable.

Expecting Borrowed Data Without Cloning
From<&str> for String calls .to_owned() internally. It's not a zero-cost reference conversion — it allocates. If you only need a reference, use AsRef<str> or Borrow<str> instead. From and Into consume ownership and often imply allocation or transformation.

Overlooking the Orphan Rule
You can implement From<YourType> for SomeExternalType only if SomeExternalType is defined in your crate or From is defined in your crate (which it isn't — it's in std). So impl From<MyStruct> for Vec<u8> is not allowed because both From and Vec are foreign. You solve this by implementing From<MyStruct> for YourWrapper or by providing a method that returns the Vec.

Real-World Usage in the Standard Library

The standard library is full of From implementations. A few that appear in everyday Rust:

  • String::from("hello") — from &str to owned String.
  • Vec::from([1, 2, 3]) — from an array to a Vec.
  • PathBuf::from("some/path") — from an OsStr or &str to an owned path.
  • Box<dyn Error>::from(e) — for converting concrete errors into trait objects.

All of these consume the input and give back an owned, independent value. They also all enable .into() — you can write let s: String = "hello".into(); and get the same result.

Collections use From to accept iterators:

let v = Vec::from([1, 2, 3]);          // from array
let v: Vec<_> = (0..5).into();         // from Range<i32>

The Iterator trait has a collect method that ultimately relies on FromIterator, but many concrete types also implement From<SomeIterator> for convenience.

From vs TryFrom

From is for conversions that cannot fail. TryFrom is the fallible cousin, returning Result<Self, Self::Error>. Use TryFrom when the input might be invalid: parsing numbers, validating UTF-8, or handling unknown enum variants.

impl TryFrom<&str> for Temperature {
    type Error = &'static str;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        // parse and validate...
    }
}

If you are ever tempted to panic! or unwrap inside a From implementation, that’s a sign you should be using TryFrom instead.

Summary

From and Into are the foundational infallible conversion traits in Rust. You implement From to teach the compiler how to build your type from another. You receive Into for free, which lets generic code accept anything convertible. The pattern of writing fn foo(x: impl Into<MyType>) makes functions flexible and idiomatic without runtime overhead.

The single most important takeaway: these traits are one-way. Implement From once, get the .into() convenience, but never expect a return path unless you explicitly implement it. When conversions might fail, reach for TryFrom and TryInto — the fallible mirror image in the standard utility trait family.