Rust provides the From
and Into
traits for converting one type into another. Mastering these two traits is crucial for beginners since they are used very frequently. I faced some questions and difficulties when I was learning them, and I think I’m not alone here. Hopefully this article will clear things up! 🦀
Table of Contents
- What is
From
trait? - What is
Into
trait? - Implement
From
gives youInto
for free - When to use
From
orInto
? - Use
TryFrom
if conversion may fail impl Into<T>
in function parameters
What is From
trait?
The From
trait has an associated function called from
for converting a value’s type to another one. Here’s the simplified definition:
trait From<T> {
// Converts to this type from the input type.
fn from(value: T) -> Self;
}
Okay that isn’t too useful… Let’s look at an example.
Suppose we have a struct called Rectangle
. We want users to easily create a rectangle from a tuple of dimensions (e.g. (10, 20)
). To support type conversion from (usize, usize)
to Rectangle
, we implement the From
trait:
#[derive(Debug, Clone, PartialEq)]
struct Rectangle {
width: usize,
height: usize,
}
// Convert (usize, usize) -> Rectangle
impl From<(usize, usize)> for Rectangle {
fn from((width, height): (usize, usize)) -> Self {
Self { width, height }
}
}
fn main() {
let rect = Rectangle::from((30, 50));
println!("{rect:?}"); // Rectangle { width: 30, height: 50 }
}
Initially, I found impl From<A> for B
tricky to read. If we read it out loud as “implement from A for B”, I got confused by whether it is “from A to B” or “from B to A” 😅. Eventually, I’ve come up with this visual cue:
You may already be using the From
trait without noticing. For example, you can convert a string slice &str
into a String
by calling String::from()
.
fn main() {
let str_slice = "Hello World!";
let string = String::from(str_slice);
println!("{string}");
}
This is because the standard library implements From
trait for String
to allow conversion from &str
:
// In Rust standard library's source code
impl From<&str> for String {
fn from(s: &str) -> String {
// ...
}
}
What is Into
trait?
The Into
trait is another trait for type conversion. It has a method called into
that turns a type into another. Here’s the simplified definition:
trait Into<T> {
// Converts this type into the (usually inferred) input type.
fn into(self) -> T;
}
Let’s head back to our Rectangle
example. We can implement Into
for (usize, usize)
to convert it into Rectangle
.
#[derive(Debug, Clone, PartialEq)]
struct Rectangle {
width: usize,
height: usize,
}
// Convert (usize, usize) -> Rectangle
impl Into<Rectangle> for (usize, usize) {
fn into(self) -> Rectangle {
Rectangle {
width: self.0,
height: self.1,
}
}
}
fn main() {
let rect: Rectangle = (30, 50).into();
println!("{rect:?}"); // Rectangle { width: 30, height: 50 }
}
💡 In practice we ONLY need to implement From
trait instead of Into
trait. We’ll explain that later in the next section.
Type Annotation
Using .into()
has a huge caveat. Rust must be able to infer the type that you’re converting “into”. If we remove the type annotation from rect
, it fails to compile:
error[E0283]: type annotations needed
--> src/main.rs:17:9
|
17 | let rect = (30, 50).into();
| ^^^^ ---- type must be known at this point
|
This is because (30, 50)
can convert into other types that are not Rectangle
, so Rust don’t know what you’re converting to! For example, Rust can convert the tuple back to a primitive array in addition to a Rectangle
:
fn main() {
let arr: [usize; 2] = (30, 50).into();
println!("{arr:?}"); // [30, 50]
let rect: Rectangle = (30, 50).into();
println!("{rect:?}"); // Rectangle { width: 30, height: 50 }
// 🚨 ERROR: Not sure what you're convering into!
let unknown = (30, 50).into();
}
Implement From
gives you Into
for free
If you look at From
trait’s documentation:
One should always prefer implementing
From
overInto
because implementingFrom
automatically provides one with an implementation ofInto
thanks to the blanket implementation¹ in the standard library.
¹ See “What are Blanket implementations in Rust?”
To put it simply:
Let’s head back to the Rectangle
example:
#[derive(Debug, Clone, PartialEq)]
struct Rectangle {
width: usize,
height: usize,
}
// Converts (usize, usize) to Rectangle
// You receive `impl Into<Rectangle> for (usize, usize)` for free!
impl From<(usize, usize)> for Rectangle {
fn from((width, height): (usize, usize)) -> Self {
Self { width, height }
}
}
fn main() {
let r1 = Rectangle::from((3, 4));
let r2: Rectangle = (3, 4).into();
assert_eq!(r1, r2);
}
As you can see, simply implementing From
trait gives us two ways to perform type conversion:
- From -
Rectange::from((3, 4))
- Into -
(3, 4).into()
💡 IMPORTANT: Both ways converts (usize, usize)
into Rectangle
. The Into
implementation you get for free has the SAME direction of type conversion as From
(i.e. tuple to Rectangle
)!
When to use From
or Into
?
If both From
and Into
are for converting type A to B, you may ask:
When should I use
from()
or.into()
?
The answer is:
- Use
.into()
if you want less verbosity- 👍 No need to import the type, no need to wrap in
from()
function call - 👎 Less obvious on what type it is converted to
- 👎 More difficult to locate how the type conversion is implemented
- 👍 No need to import the type, no need to wrap in
- Use
from()
if you want to be more explicit on what type it converts to
Verbosity
Using from()
is more verbose than .into()
because you may need to import the type that it’s converted to, and wrapping in function calls make the code longer.
If the struct has a long name (e.g. UserDefinedRectangle
), then using .into()
is much cleaner:
fn is_same(r1: UserCreatedRectangle, r2: UserCreatedRectangle) -> bool {
r1.width == r2.width && r1.height == r2.height
}
fn main() {
let dim_1 = (3, 4);
let dim_2 = (5, 6);
// 🤢 `from()` is a long beam of text!
is_same(UserCreatedRectangle::from(dim_1), UserCreatedRectangle::from(dim_2));
// 😍 `.into()` is very clean
is_same(dim_1.into(), dim_2.into());
}
Moreover, it’s common to use .into()
instead of constructors (e.g. new()
) to reduce verbosity. For example:
#[derive(Debug, Clone, PartialEq)]
struct Rectangle {
width: usize,
height: usize,
}
impl Rectangle {
// Rectangle constructor
fn new(width: usize, height: usize) -> Self {
Self { width, height }
}
}
impl From<(usize, usize)> for Rectangle {
fn from((width, height): (usize, usize)) -> Self {
Self { width, height }
}
}
fn compare_rectangles(r1: Rectangle, r2: Rectangle) -> bool {
r1.width == r2.width && r1.height == r2.height
}
fn main() {
// 🤢 Constructors are verbose
assert!(compare_rectangles(
Rectangle::new(2, 3),
Rectangle::new(2, 3)
));
// 😍 Much cleaner with `.into()`
assert!(compare_rectangles((2, 3).into(), (2, 3).into()))
}
Locating implementation
One drawback of .into()
over from()
is that it’s more difficult to find how the type is converted.
Usually from()
is directly associated to a concrete struct/enum, so using “Go to Definition” in your editor will directly jump to the implementation 👇
However, the .into()
method is usually derived from a blanket implementation of From<T>
, so using “Go to Definition” on .into()
will jump to the Rust’s blanket implementation! 👇
To see how the type conversion is implemented, you’ll need to find where impl From<(usize, usize)> for Rectangle
is located. While our example places this implementation next to the Rectangle
’s definition, this may not be the case for other libraries.
Use TryFrom
if conversion may fail
Sometimes converting from one type to another may fail. A noob may call panic if type conversion fail, but this may be a bad idea since the entire program will exit immediately.
For example, let’s say every Rectangle
must not have a zero area:
impl From<(usize, usize)> for Rectangle {
fn from((width, height): (usize, usize)) -> Self {
if width == 0 || height == 0 {
// 👎👎 AVOID panics! Handle it gracefully!
panic!("Zero area rectangles are not allowed");
}
Rectangle { width, height }
}
}
Instead, use the TryForm
trait. The conversion will return a Result
according to the trait’s definition. You’ll need to specify the input type T
and also the error type Error
if conversion fails:
// Simplified definition
trait TryFrom<T> {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
Here’s an example:
#[derive(Debug, Clone, PartialEq)]
struct Rectangle {
width: usize,
height: usize,
}
// We prefer enums over strings for errors
#[derive(Debug)]
enum RectangleConversionError {
ZeroArea,
}
// ✅ Rectangle::try_from((usize, usize))
impl TryFrom<(usize, usize)> for Rectangle {
type Error = RectangleConversionError;
fn try_from((width, height): (usize, usize)) -> Result<Self, Self::Error> {
if width == 0 || height == 0 {
Err(RectangleConversionError::ZeroArea)
} else {
Ok(Rectangle { width, height })
}
}
}
fn main() {
let r1 = Rectangle::try_from((5, 2));
println!("{r1:?}"); // Ok(Rectangle { width: 5, height: 2 })
let r2 = Rectangle::try_from((0, 2));
println!("{r2:?}"); // Err(ZeroArea)
}
💡 Similar to From
, implementing TryFrom
gives you TryInto
for free!
fn main() {
// Don't forget to add type annotation, so Rust knows what you're casting into
// vvvvvvvvvvvvvvvvvvvvvv
let result: Result<Rectangle, _> = (0, 0).try_into();
assert!(result.is_err());
}
impl Into<T>
in function parameters
One trick that you can do is to use impl Into<T>
as the type of a function parameter instead of just T
. This is known as “impl trait type”.
For example, instead of requiring each call’s argument to add .into()
, such as:
fn compare_rectangles(r1: Rectangle, r2: Rectangle) -> bool {
r1.width == r2.width && r1.height == r2.height
}
fn main() {
// I need to add `.into()` for each argument
compare_rectangles((3, 5).into(), (3, 6).into());
}
You can specify the parameter type as impl Into<Rectangle>
:
// vvvvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvvvv
fn compare_rectangles(r1: impl Into<Rectangle>, r2: impl Into<Rectangle>) -> bool {
let r1: Rectangle = r1.into();
let r2: Rectangle = r2.into();
r1.width == r2.width && r1.height == r2.height
}
fn main() {
// Notice I don't need to call `.into()` for the arguments
println!("{}", compare_rectangles((3, 5), (3, 5))); // true
println!("{}", compare_rectangles((3, 5), (5, 3))); // false
}
This works because impl Into<Rectangle>
basically means:
“I accept any types that can turn into a
Rectangle
(i.e. implementsInto<Rectangle>
)”
Pros and cons:
- 👍 Less verbose to call
.into()
for every argument. This is especially useful if the function is frequently called using arguments with.into()
- 👎 Function signature looks more complicated
That’s pretty much all I want to share. Don’t forget to like and share this post if you found it useful, cheers! 🙌
Top comments (1)
Excellent breakdown, thank you!