When one struct isn't enough
You're building a user management system. Your database driver returns a raw row with fields like user_id, email_addr, and created_at_epoch. Your application logic expects a clean User struct with id, email, and created_at. You could manually copy fields everywhere, but that's error-prone and repetitive. You need a way to say, "Turn this database row into a User," and have Rust handle the transformation safely.
Rust solves this with the From and Into traits. These traits let you define type conversions that are explicit, composable, and ergonomic. You write the conversion logic once, and the compiler generates the reverse direction for free. You also get type inference at call sites, which makes your code flexible and easy to read.
The recipe and the button
Think of From as a factory blueprint. You define exactly how to build a User from a DbRow. The blueprint lives on the User type because User knows what it needs to be constructed.
Into is the request button. When you have a DbRow and you want a User, you press the button by calling .into(). You don't need to write a separate blueprint for "making a DbRow into a User." The compiler generates that automatically based on your From implementation.
This asymmetry is intentional. You implement From<T> for U to define how U is created from T. The standard library provides a blanket implementation that gives you Into<U> for T whenever From<T> is implemented. You get two-way conversion power by writing code for only one direction.
Minimal example
Here is the basic pattern. Implement From on the target type, then use .into() on the source.
struct DbRow {
user_id: i32,
email_addr: String,
}
struct User {
id: i32,
email: String,
}
// Implement From<Source> for Target.
// This defines how to create a User from a DbRow.
impl From<DbRow> for User {
fn from(row: DbRow) -> Self {
User {
id: row.user_id,
email: row.email_addr,
}
}
}
fn main() {
let row = DbRow {
user_id: 42,
email_addr: "alice@example.com".to_string(),
};
// Use .into() to convert.
// The compiler finds the From impl and calls User::from(row).
let user: User = row.into();
println!("User: {} ({})", user.email, user.id);
}
Convention: implement From, not Into
The community convention is strict: always implement From. Never implement Into directly.
If you implement Into, you have to write the conversion logic manually, and you lose the automatic From generation. Implementing From gives you Into for free via the blanket impl. It also keeps the logic attached to the type being created, which matches Rust's construction patterns. If you see impl Into in a codebase, it's usually a sign that the author missed the blanket impl. Stick to From.
How the compiler connects the dots
When you call row.into(), Rust looks for a type that implements Into<User>. It finds the blanket implementation in std:
// Simplified blanket impl from the standard library
impl<T, U> Into<U> for T where U: From<T> {
fn into(self) -> U {
U::from(self)
}
}
This says: "Any type T can be converted into U if U implements From<T>." The implementation just calls U::from(self). The conversion is zero-overhead. The compiler inlines the call, and the result is identical to calling User::from(row) directly.
The from method takes self by value. This means the source value is moved into the conversion. The DbRow is consumed. You cannot use row after calling .into(). This is a safety feature. It prevents accidental copies and makes ownership flow explicit. If you need to keep the source, clone it first: let user = row.clone().into();.
Type inference makes .into() powerful
The .into() method shines when the compiler can infer the target type. In the example above, let user: User = row.into(); works because the variable type tells the compiler what Into needs to produce.
This enables flexible APIs. You can write a function that accepts anything convertible to User:
fn create_account<T: Into<User>>(data: T) {
let user = data.into();
// ... process user
}
fn main() {
// Pass a DbRow
create_account(DbRow { user_id: 1, email_addr: "bob@test.com".to_string() });
// Pass a User directly (Into is reflexive for the same type)
create_account(User { id: 2, email: "carol@test.com".to_string() });
}
The function works with DbRow, User, or any other type that implements Into<User>. You don't need separate overloads. The trait bound handles the variety. Implement From, get Into for free, and write generic code that accepts any compatible input.
Realistic conversion with validation
Real-world conversions often involve more than renaming fields. You might need to parse strings, calculate derived values, or validate data. From can handle all of this, but it has a hard constraint: it must always succeed.
struct ConfigInput {
raw_port: String,
host: String,
}
struct ServerConfig {
port: u16,
host: String,
}
impl From<ConfigInput> for ServerConfig {
fn from(input: ConfigInput) -> Self {
// Parse the port string into a u16.
// If parsing fails, this panics.
let port: u16 = input.raw_port.parse()
.expect("Port must be a valid number");
ServerConfig {
port,
host: input.host,
}
}
}
This works if you control the input and can guarantee validity. If the input comes from a user or an external file, parsing might fail. From cannot return a Result. The signature is fixed: fn from(T) -> Self. If you try to return Result<Self, Error>, the compiler rejects you with E0308 (mismatched types). The function must return Self, nothing else.
When conversion can fail, From is the wrong tool. Use TryFrom instead. TryFrom returns Result<Self, E>, allowing you to propagate errors gracefully. The pattern is the same: implement TryFrom<Source> for Target, then use .try_into() at the call site.
Pitfalls and compiler errors
E0308: mismatched types in From
If you attempt to return a Result from From::from, you get E0308. The compiler expects Self, but you provided Result<Self, E>. This is a common mistake when migrating from languages where casting can throw exceptions. Rust separates infallible and fallible conversions.
Fix: Switch to TryFrom if the conversion can fail. Keep From only for conversions that are guaranteed to succeed.
E0277: trait bound not satisfied
If you call .into() and the compiler cannot find a conversion, you get E0277. This usually means you forgot to implement From, or the types don't match.
let row = DbRow { user_id: 1, email_addr: "test@test.com".to_string() };
let user: User = row.into();
// error[E0277]: the trait bound `User: From<DbRow>` is not satisfied
Fix: Add the impl From<DbRow> for User block. Check that the source and target types are correct. Remember that From is directional. From<DbRow> for User allows DbRow to become User, not the other way around.
Ownership traps
From consumes the source. If you try to use the source after conversion, you get E0382 (use of moved value).
let row = DbRow { user_id: 1, email_addr: "test@test.com".to_string() };
let user: User = row.into();
println!("Original row: {:?}", row);
// error[E0382]: use of moved value: `row`
Fix: Clone the source if you need to keep it. Or restructure the code to pass ownership where it's needed. If you only need a reference, consider AsRef or a custom method that takes &self.
Generic resolution ambiguity
If a type implements Into for multiple targets, and the compiler cannot infer which one you want, you get a type error.
// Hypothetical: User implements Into<String> and Into<Vec<u8>>
let user = User { id: 1, email: "test@test.com".to_string() };
let data = user.into();
// error[E0283]: type annotations needed
Fix: Add a type annotation. let data: String = user.into();. Or use the explicit method: let data = user.to_string();. .into() relies on context. Provide the context when it's missing.
Decision: when to use From vs alternatives
Use From when you need an infallible, consuming conversion between two types. Use TryFrom when the conversion might fail and you need to return a Result. Use a custom method like from_row when the conversion requires external context, like a database connection or configuration, that doesn't fit inside a trait impl. Use AsRef or AsMut when you need a zero-cost reference conversion, like turning a String into a &str. Use a simple function when the conversion is one-off and doesn't benefit from trait composition.