Rust: From vs Into traits

2025/01/06

Why does implementing From<T> on U enable calling T.into() to get U?

impl From<A> for B {
    fn from(a: A) -> B {
        // Convert A to B
    }
}

By implementing the from() static method on B, you can convert an instance of A to B:

let a: A = ...;
let b: B = B::from(a); // This works

However, in practice, we often avoid using this directly, as it isn’t considered idiomatic Rust. Instead, we do the following:

let b: B = a.into();

Q: Wait a second, where does this .into() come from? We didn’t implement any into() function on A.

A: Correct, you didn’t implement it yourself. Rust provides it automatically. How?

This involves blanket implementations in Rust, which is a way of implementing something for all types. Essentially, this is done using a construct like:

impl<T> Trait1 for T {
    ...
}

In the case of the From trait, the Rust standard library provides the following blanket implementation:

impl<T, U> Into<U> for T  // For all types T, implement Into<U>
where
    U: From<T>,           // If U already implements From<T>
{
    fn into(self) -> U {
        U::from(self)
    }
}

This means that the standard library provides a blanket implementation of the Into<U> trait for all T where U already has an implementation of From<T>. In simpler terms, the compiler automatically implements the counterpart Into for you.

Effectively, it is as if the compiler wrote this for you:

impl Into<B> for A {
    fn into(self) -> B {
        B::from(self)
    }
}

Thus, for any instance of A, you can call .into() to convert it to B.