When you write let n: MyNumber = 42.into(); in Rust, the compiler is looking for one thing: an implementation of the From trait that turns an i32 into a MyNumber. From and Into are the standard library's two halves of the same idea. From says "here is how to build me out of that." Into says "here is how to turn me into that." You almost always write the first one, and the standard library hands you the second one for free.
This guide covers how the pair works, why implementing From is the only correct choice, how the question mark operator uses it to convert error types, and the four mistakes that stop these conversions compiling.
What From and Into Actually Are
Both are traits in std::convert, and both are in the prelude, so you never need to import them.
From<T> has a single method that consumes a T and returns Self:
pub trait From<T> {
fn from(value: T) -> Self;
}
Into<U> has a single method that consumes Self and returns a U:
pub trait Into<U> {
fn into(self) -> U;
}
Notice that both take ownership. A conversion moves the input value. If you need to look at data as another type without owning it, that is a job for AsRef, which is covered further down.
Why Implementing From Gives You Into for Free
The standard library contains this blanket implementation:
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
Read it in plain English: for any type T, and any type U that knows how to be built from T, T can turn itself into U. So the moment you write impl From<Celsius> for Fahrenheit, every Celsius value gains a .into() method that produces a Fahrenheit.
That blanket implementation is also the reason you must never write impl Into<U> for T by hand. Your hand-written implementation would overlap with the one in the standard library, and the compiler rejects overlapping implementations. Write From, always.
Writing Your First From Implementation
Here is a newtype that wraps a temperature, with a conversion from a plain f64:
struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
Both call styles now work, and they compile to the same thing:
let boiling = Celsius(100.0);
// Explicit, using the trait you wrote.
let f1 = Fahrenheit::from(boiling);
// Implicit, using the blanket Into.
let freezing = Celsius(0.0);
let f2: Fahrenheit = freezing.into();
Prefer Fahrenheit::from(x) when the target type is not obvious from the surrounding code, because it names the destination. Prefer x.into() when the destination is already written down, such as in a let with a type annotation or in an argument position.
How the Question Mark Operator Uses From
This is where From stops being a convenience and starts being infrastructure. When you write ? on a Result, the compiler expands it to something close to this:
match result {
Ok(v) => v,
Err(e) => return Err(From::from(e)),
}
The error is passed through From::from on the way out. So if your function returns Result<T, AppError>, every error type that can appear inside it only needs an impl From<ThatError> for AppError and ? handles the rest.
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Io(io::Error),
BadNumber(ParseIntError),
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> Self {
AppError::Io(e)
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::BadNumber(e)
}
}
With those two implementations in place, this function compiles even though it produces two unrelated error types:
use std::fs;
fn read_port(path: &str) -> Result<u16, AppError> {
let raw = fs::read_to_string(path)?; // io::Error -> AppError
let port: u16 = raw.trim().parse()?; // ParseIntError -> AppError
Ok(port)
}
Remove either From implementation and the matching ? stops compiling. That single mechanism is why error-handling crates in the Rust ecosystem generate From implementations for you rather than inventing their own conversion trait.
Accepting Into in Function Signatures
A function that takes String forces every caller holding a &str to write .to_string(). A function that takes impl Into<String> accepts both:
fn greet(name: impl Into<String>) {
let name = name.into();
println!("Hello, {name}");
}
greet("Ada"); // &str
greet(String::from("Grace")); // String
The older generic form means exactly the same thing and is still common in library code:
fn greet<T: Into<String>>(name: T) {
let name: String = name.into();
println!("Hello, {name}");
}
Two things to know before you reach for this everywhere. First, it is generic, so the compiler produces a separate copy of the function for each argument type you call it with. On a large function called with many types, that grows the binary. Second, it only helps when the function genuinely needs an owned String. If the body just reads the text, take &str and let deref coercion do the work at no cost.
From vs Into: Which One to Implement
Implement From. There is only one case where you cannot, and it is the orphan rule.
Rust lets you implement a trait for a type when either the trait or the type is local to your crate. From belongs to the standard library, so impl From<Vec<u8>> for String is rejected: both sides are foreign to your crate.
// Does not compile. Both String and From are foreign to this crate.
// impl From<Vec<u8>> for String { ... }
// Compiles. Wrapper is ours, so we own one side of the implementation.
struct Wrapper(String);
impl From<Vec<u8>> for Wrapper {
fn from(bytes: Vec<u8>) -> Self {
Wrapper(String::from_utf8_lossy(&bytes).into_owned())
}
}
The usual fix is a newtype wrapper like the one above, which is a cheap and idiomatic pattern in Rust rather than a workaround.
One more implementation you get without asking: impl<T> From<T> for T. Every type can be converted from itself, which is why let s: String = my_string.into(); compiles and does nothing.
TryFrom and TryInto for Conversions That Can Fail
From promises the conversion always succeeds. When it can fail, use TryFrom, which returns a Result and carries its own error type. The same blanket implementation trick applies, so implementing TryFrom gives you TryInto.
use std::convert::TryFrom;
#[derive(Debug)]
struct Percentage(u8);
#[derive(Debug)]
struct OutOfRange(i32);
impl TryFrom<i32> for Percentage {
type Error = OutOfRange;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if (0..=100).contains(&value) {
Ok(Percentage(value as u8))
} else {
Err(OutOfRange(value))
}
}
}
Using it:
let good = Percentage::try_from(75); // Ok(Percentage(75))
let bad = Percentage::try_from(140); // Err(OutOfRange(140))
// Or with the question mark operator.
fn parse_share(raw: i32) -> Result<Percentage, OutOfRange> {
let share = Percentage::try_from(raw)?;
Ok(share)
}
The standard library follows the same rule for numeric types. Widening conversions that cannot lose data implement From. Narrowing conversions implement TryFrom:
let big: u32 = u32::from(200u8); // always fits, From
let small = u8::try_from(300u32); // Err, 300 does not fit in u8
let ok = u8::try_from(200u32).unwrap(); // Ok(200)
Reach for as only when you actively want the silent truncation, because 300u32 as u8 compiles and quietly produces 44.
Choosing Between From, AsRef, and Borrow
Three traits look similar from a distance. They answer different questions.
| Trait | Question it answers | Ownership | Typical use |
|---|---|---|---|
From / Into | How do I build a U out of a T? | Consumes the input | Constructors, error conversion, flexible arguments |
AsRef<U> | Can I view this as a &U cheaply? | Borrows | Functions that only read, such as taking impl AsRef<Path> |
Borrow<U> | Can I view this as a &U with matching Hash and Eq? | Borrows | Hash map lookups, where HashMap<String, V> accepts a &str key |
A practical rule: if the function needs to keep the value, take impl Into<T>. If it only reads the value, take impl AsRef<T> or a plain reference.
use std::path::Path;
// Reads only, so it borrows. Accepts &str, String, PathBuf and Path.
fn file_stem(p: impl AsRef<Path>) -> Option<String> {
p.as_ref()
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
}
Four Mistakes That Break These Conversions
1. Calling into With No Type Annotation
This is the most common compiler error in this area:
// error[E0282]: type annotations needed
let x = "hello".into();
Into is generic over the destination, and nothing in that line says what the destination is. Give the compiler a target:
let x: String = "hello".into();
// or
let x = String::from("hello");
The same problem appears at call sites. If a function takes impl Into<T> and you pass something.into(), the compiler has two unknowns and gives up. Pass the value directly instead.
2. Implementing Into by Hand
// Conflicts with the blanket impl in the standard library.
// impl Into<Fahrenheit> for Celsius { ... }
Write impl From<Celsius> for Fahrenheit and delete the Into.
3. Using From for a Conversion That Can Fail
If your from body contains an unwrap, a panic!, or a silent clamp, the signature is lying to callers. Move it to TryFrom and return the error. A conversion that panics on bad input is far harder to debug than one that returns Err.
4. Hiding Expensive Work Behind into
.into() reads as free. Callers do not expect it to allocate, copy a buffer, or hit an index. Keep From implementations cheap and obvious. When the conversion is genuinely expensive, give it a named method such as to_owned_report() so the cost is visible at the call site.
For a closer look at the borrowing side of this, see our guide to Rust slices, which covers when to take a reference instead of converting at all. If you are weighing Rust up for server work, our overview of Rust for backend development and our roundup of the top Rust frameworks both go into the ecosystem around these traits.
A Short Checklist
Before you ship a conversion, run through this:
The conversion cannot fail, so it is a
Fromand not aTryFromYou implemented
From, notIntoAt least one of the two types is defined in your crate
The body allocates no more than a caller would expect
Error types used with
?have aFromimplementation into the function's error type
Frequently Asked Questions
Should I Implement From or Into?
Implement From. The blanket implementation in the standard library gives you Into automatically, and a hand-written Into overlaps with it and will not compile.
Why Does .into() Give a Type Annotations Needed Error?
Into is generic over the destination, so the compiler cannot work out what you want. Annotate the binding with let x: String = "hello".into();, or call the concrete constructor with String::from("hello").
Can I Implement From for a Type I Did Not Define?
Only when the other side of the conversion belongs to your crate. The orphan rule requires either the trait or the type to be local, and From is owned by the standard library. Wrapping the foreign type in a newtype struct is the usual way around it.
What Is the Difference Between From and TryFrom?
From is for conversions that always succeed and returns the target type. TryFrom is for conversions that can fail and returns a Result. The standard library follows the same split for numbers: u8 to u32 uses From, and u32 to u8 uses TryFrom.
How Does the Question Mark Operator Use From?
? passes the error through From::from on its way out of the function. Any error type with an implementation into your function's error type therefore works with ? and needs no conversion code at the call site.
Summary
From and Into are one mechanism seen from two directions. You write impl From<A> for B, and the standard library's blanket implementation gives every A an .into() that produces a B. That single implementation also teaches the question mark operator how to convert error types, which is what makes Rust error handling short instead of noisy.
Implement From, never Into. Move to TryFrom the moment the conversion can fail, because a Result in the signature is honest and a panic inside from is not. Take impl Into<T> when your function needs to own the value and impl AsRef<T> when it only needs to read it. Watch for the type-annotation error on a bare .into(), and remember that the orphan rule means a newtype wrapper is often the shortest path to the conversion you want.


![#[cfg] Conditional Compilation in Rust](https://strapi-images-aws-s3.s3.us-west-2.amazonaws.com/Embedded_Link_44_06c388963d.png)
![When to use Arrays and Vectors in Rust — Array[] vs Vectors<>](https://strapi-images-aws-s3.s3.us-west-2.amazonaws.com/Arrays_vs_Vectors_in_Rust_a110e678aa.png)