From and Into in Rust
A concise guide about From & Into traites.
Rust provides the From and Into traits for converting one type into another in a consistent and type-safe way.
They are commonly used when working with custom types, error handling, APIs, and generic code.
1. The From Trait
The From trait defines how to create a value of one type from another type.
Its simplified definition is:
1
2
3
trait From<T>: Sized {
fn from(value: T) -> Self;
}
The important points are:
Tis the source type.Selfis the destination type.fromcreates a destination value from the source value.
Example
1
2
3
let number = String::from("42");
println!("{number}");
Here:
1
&str ──From──> String
The String type implements From<&str>.
You can also convert numeric types:
1
2
3
let number = i32::from(10_i16);
println!("{number}");
The conversion is valid because i32 implements:
1
From<i16>
2. Implementing From for a Custom Type
Suppose we have a custom UserId type:
1
struct UserId(u64);
We can define how to create a UserId from a u64:
1
2
3
4
5
impl From<u64> for UserId {
fn from(value: u64) -> Self {
UserId(value)
}
}
Now we can write:
1
2
3
let user_id = UserId::from(42);
println!("{}", user_id.0);
The conversion is explicit and type-safe.
3. The Into Trait
The Into trait defines how a value is converted into another type.
Its simplified definition is:
1
2
3
trait Into<T>: Sized {
fn into(self) -> T;
}
Example:
1
2
3
let text: String = "hello".into();
println!("{text}");
The compiler knows that the destination type is String, so it uses the appropriate conversion.
The type annotation is important:
1
let text: String = "hello".into();
Without the annotation, Rust may not know which destination type you want:
1
let text = "hello".into(); // Usually cannot infer the target type
4. From Automatically Provides Into
When you implement:
1
From<Source> for Destination
Rust automatically provides the reverse-looking conversion:
1
Into<Destination> for Source
For example:
1
2
3
4
5
6
7
struct UserId(u64);
impl From<u64> for UserId {
fn from(value: u64) -> Self {
UserId(value)
}
}
You can use both forms:
1
2
3
let id1 = UserId::from(42);
let id2: UserId = 42.into();
Both create a UserId from a u64.
Conceptually:
1
2
3
4
5
6
7
From:
UserId::from(42)
Into:
42.into() -> UserId
The conversion implementation is defined only once, through From.
5. Prefer Implementing From Instead of Into
If you control both types, prefer implementing From:
1
2
3
4
5
impl From<u64> for UserId {
fn from(value: u64) -> Self {
UserId(value)
}
}
Avoid implementing only Into:
1
2
3
impl Into<UserId> for u64 {
// Usually not recommended when From can be implemented
}
Why?
Implementing From automatically gives you Into, while implementing only Into does not automatically give you From.
Therefore, From is the canonical direction for defining conversions.
6. From Is Intended for Infallible Conversions
From should generally be used when conversion cannot fail.
For example:
1
2
3
4
5
impl From<u64> for UserId {
fn from(value: u64) -> Self {
UserId(value)
}
}
Every u64 can become a UserId, so this conversion is infallible.
However, consider parsing a string into an integer:
1
let number: u32 = "42".parse().unwrap();
Parsing can fail, so it should not use From<&str> for u32.
Instead, Rust uses FromStr:
1
2
3
use std::str::FromStr;
let number = u32::from_str("42");
The result is:
1
Result<u32, ParseIntError>
This makes the possibility of failure explicit.
General guideline
| Conversion | Appropriate trait |
|---|---|
| Cannot fail | From / Into |
| Can fail | TryFrom / TryInto |
| Parsing text | FromStr |
| Formatting as text | Display / ToString |
7. TryFrom and TryInto
For conversions that may fail, Rust provides TryFrom and TryInto.
Simplified definitions:
1
2
3
4
5
trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
1
2
3
4
5
trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
Example:
1
2
3
4
5
use std::convert::TryFrom;
let value = u8::try_from(300_u16);
println!("{value:?}");
Output:
1
Err(TryFromIntError(...))
The conversion fails because 300 cannot fit into a u8.
A valid conversion succeeds:
1
2
3
let value = u8::try_from(100_u16);
assert_eq!(value.unwrap(), 100);
8. From in Error Handling
One of the most important uses of From is converting different error types into a common application error.
Suppose we define:
1
2
3
4
5
6
use std::io;
#[derive(Debug)]
enum AppError {
Io(io::Error),
}
Implement From:
1
2
3
4
5
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
Now an io::Error can automatically become an AppError.
This is especially useful with the ? operator:
1
2
3
4
5
fn read_file() -> Result<String, AppError> {
let content = std::fs::read_to_string("config.txt")?;
Ok(content)
}
What happens internally?
1
2
3
4
5
6
7
8
9
10
11
read_to_string()
|
v
Result<String, io::Error>
|
| error occurs
v
From::from(io::Error)
|
v
Result<String, AppError>
The ? operator uses the available conversion to transform the error into the function’s declared error type.
This is why implementing From for application errors makes error propagation much easier.
9. Multiple Error Conversions
An application may have several possible error sources:
1
2
3
4
5
6
7
use std::{io, num::ParseIntError};
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
}
Implement conversions:
1
2
3
4
5
6
7
8
9
10
11
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
impl From<ParseIntError> for AppError {
fn from(error: ParseIntError) -> Self {
AppError::Parse(error)
}
}
Now both operations can use ?:
1
2
3
4
5
6
fn load_number() -> Result<u32, AppError> {
let content = std::fs::read_to_string("number.txt")?;
let number = content.trim().parse::<u32>()?;
Ok(number)
}
Each error is automatically converted into AppError.
10. Generic Functions Using Into
Into is useful when you want a function to accept several input types that can all be converted into one target type.
Example:
1
2
3
4
5
fn greet(name: impl Into<String>) {
let name: String = name.into();
println!("Hello, {name}!");
}
Now the function accepts different types:
1
2
greet("Amir");
greet(String::from("Amir"));
Both work because both arguments can be converted into String.
You can also write the generic form explicitly:
1
2
3
4
5
6
7
8
fn greet<T>(name: T)
where
T: Into<String>,
{
let name: String = name.into();
println!("Hello, {name}!");
}
The impl Into<String> syntax is shorthand for this kind of generic bound.
11. From and API Design
Consider an API that accepts only String:
1
2
3
fn set_name(name: String) {
println!("{name}");
}
Calling it with a string literal requires an explicit conversion:
1
set_name("Amir".to_string());
Alternatively, define the API using Into<String>:
1
2
3
4
5
fn set_name(name: impl Into<String>) {
let name = name.into();
println!("{name}");
}
Now both are accepted:
1
2
set_name("Amir");
set_name(String::from("Amir"));
This can make APIs more ergonomic.
However, Into should not be used automatically everywhere. If the function only needs to temporarily read text, borrowing is often better:
1
2
3
fn print_name(name: &str) {
println!("{name}");
}
Use Into<String> when the function genuinely needs to own or normalize the input.
12. From vs Into
| Feature | From | Into |
|---|---|---|
| Direction | Source to destination | Self to target |
| Syntax | Target::from(value) | value.into() |
| Implementation | Usually implement this | Automatically provided by From |
| Type inference | Usually clearer | Destination type may be required |
| Common use | Defining conversions | Generic APIs and concise conversions |
Example:
1
2
3
let value1 = String::from("hello");
let value2: String = "hello".into();
Both perform the same conceptual conversion.
13. From Is Not the Same as as
Rust also supports primitive casting with as:
1
let number = 10_u32 as u64;
However, as and From have different purposes.
as
Used mainly for primitive casts:
1
let value = 10_u32 as u64;
From
Used for defined semantic conversions:
1
let value = u64::from(10_u32);
From communicates that the conversion is supported as part of the type’s API.
It also avoids some potentially surprising casts.
For example, narrowing integer conversions using as can truncate:
1
2
3
let value = 300_u16 as u8;
assert_eq!(value, 44);
A checked conversion is safer:
1
2
3
let value = u8::try_from(300_u16);
assert!(value.is_err());
14. From and Ownership
Into consumes self:
1
2
3
trait Into<T> {
fn into(self) -> T;
}
Therefore, calling .into() may move the original value.
Example:
1
2
3
4
5
let text = String::from("hello");
let converted: Vec<u8> = text.into_bytes();
// `text` can no longer be used here.
Similarly:
1
2
3
4
5
let text = String::from("hello");
let converted: SomeType = text.into();
// `text` was moved into the conversion.
If you need to preserve the original value, use a borrowed conversion or clone when appropriate:
1
2
3
4
5
6
let text = String::from("hello");
let converted = text.clone().into();
// `text` is still available.
println!("{text}");
The exact available conversions depend on the implemented traits.
15. Common Standard Library Examples
Rust uses From extensively:
1
2
3
4
let text = String::from("hello");
let bytes = Vec::<u8>::from("hello".as_bytes());
let number = u64::from(10_u32);
let path = std::path::PathBuf::from("/tmp/file.txt");
Other examples include:
1
2
3
let boxed = Box::from(42);
let option = Option::from(42);
let result = Result::<i32, ()>::from(Ok(42));
Some conversions are also available through Into:
1
2
3
let text: String = "hello".into();
let path: std::path::PathBuf = "/tmp/file.txt".into();
let option: Option<i32> = 42.into();
The destination type often determines which implementation the compiler selects.
16. Important Coherence Rule
You cannot implement arbitrary conversions between any two types.
Rust’s orphan rules restrict trait implementations when neither the trait nor the type is defined in your crate.
For example, you generally cannot implement a foreign trait for two foreign types:
1
2
3
4
// Not allowed in ordinary Rust code:
impl From<String> for Vec<u8> {
// ...
}
Both From and String are defined outside your crate.
However, you can implement From for your own type:
1
2
3
4
5
6
7
struct UserId(u64);
impl From<u64> for UserId {
fn from(value: u64) -> Self {
UserId(value)
}
}
Here, UserId is local to your crate, so the implementation is allowed.
17. Mental Model
Think of From as defining a constructor-like conversion:
1
2
3
4
5
Source value
|
| From
v
Destination value
For example:
1
2
3
4
5
u64
|
| UserId::from(...)
v
UserId
Into is the same relationship viewed from the source value:
1
2
3
4
5
u64
|
| .into()
v
UserId
There is one conversion relationship, but two ways to express it.
Key Takeaways
From<T> for Udefines how to convertTintoU.U::from(value)performs the conversion explicitly.- Implementing
Fromautomatically provides the correspondingIntoimplementation. value.into()is concise but often requires the destination type to be known.- Prefer implementing
Frominstead of implementingIntodirectly. - Use
Fromfor infallible conversions. - Use
TryFromandTryIntofor fallible conversions. Fromis especially useful for converting errors when using?.Intocan make generic APIs accept multiple convertible input types.- Conversions using
Intoconsume the source value unless the conversion is implemented for a reference. Fromis a trait-based conversion mechanism, not merely a primitive cast likeas.