From and Into in Rust: converting between types
How From and Into work in Rust: implementing From for your types, calling .into(), TryFrom for fallible conversions, and From for ? errors.
From and Into are the standard traits for infallible conversions: turning one type into another when it can't fail.
let s = String::from("crab"); // &str -> String
let n = i64::from(42_i32); // i32 -> i64 (always fits)
let v: Vec<u8> = "hi".into(); // &str -> Vec<u8>
Implement From, get Into for free
You implement From<Source> for Target. The standard library has a blanket impl that then gives you Into<Target> for Source automatically, so never implement Into yourself.
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)
}
}
fn main() {
let boiling = Fahrenheit::from(Celsius(100.0));
let body: Fahrenheit = Celsius(37.0).into();
println!("{} {:.1}", boiling.0, body.0);
}
212 98.6
.into() needs to know the target type, from a let annotation, a function parameter, or a return type. Without one:
fn main() {
let x = 5_i32.into();
println!("{x}");
}
error[E0283]: type annotations needed
Where From matters most: errors and ?
The ? operator converts errors with From::from. If your error type implements From<std::io::Error> and From<ParseIntError>, one function can use ? on both kinds of failure and get your error type out. thiserror's #[from] attribute writes those impls for you; anyhow::Error implements From for every standard error.
Flexible parameters with Into
A constructor that takes impl Into<String> accepts both &str and String, without forcing callers to write .to_string():
fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
Use this for public APIs where it helps callers; don't use it everywhere, since it makes signatures harder to read and every generic parameter adds compile time.
TryFrom and TryInto: conversions that can fail
When a conversion can fail (an i64 to a u8, a string to an email address, a raw number to an enum), implement TryFrom instead. It returns a Result and has an associated Error type:
impl TryFrom<u8> for Level {
type Error = String;
fn try_from(n: u8) -> Result<Self, Self::Error> { ... }
}
You then get try_into() for free, just like into().
Related traits
FromStrpowers.parse(): implement it to parse text into your type.AsRef<str>/AsRef<Path>: cheap reference conversions for parameters (fn open(path: impl AsRef<Path>)).ashandles primitive numeric casts, with truncation; preferFrom/TryFromwhen they exist.
Common mistakes
- Implementing a lossy conversion as
From.Fromshould never fail or lose information. UseTryFrom, or a named method. - Expecting
Fromin both directions. Each direction is a separate impl. - Implementing
Intodirectly. ImplementFromand let the blanket impl do the rest. - An enum with a variant named
ErrorimplementingTryFrom. Inside the impl,Self::Errorbecomes ambiguous (the variant or the associated type?) and rustc rejects it. Write the concrete type instead:-> Result<Self, String>.
Example
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
enum Level {
Info,
Warn,
Critical,
}
impl TryFrom<u8> for Level {
type Error = String;
fn try_from(n: u8) -> Result<Self, Self::Error> {
match n {
0 => Ok(Level::Info),
1 => Ok(Level::Warn),
2 => Ok(Level::Critical),
other => Err(format!("no level {other}")),
}
}
}
struct LogLine {
level: Level,
message: String,
}
impl LogLine {
fn new(level: Level, message: impl Into<String>) -> Self {
Self { level, message: message.into() }
}
}
impl From<LogLine> for String {
fn from(line: LogLine) -> Self {
format!("[{:?}] {}", line.level, line.message)
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", format!("{self:?}").to_uppercase())
}
}
fn main() {
let a = LogLine::new(Level::Info, "server started");
let b = LogLine::new(Level::Warn, String::from("disk at 91%"));
let rendered: Vec<String> = vec![a.into(), String::from(b)];
println!("{rendered:?}");
for raw in [1_u8, 2, 9] {
let parsed: Result<Level, _> = raw.try_into();
match parsed {
Ok(level) => println!("{raw} -> {level}"),
Err(e) => println!("{raw} -> error: {e}"),
}
}
let big: i64 = 300_i32.into();
println!("{big} {:?}", u8::try_from(big));
}
Output:
["[Info] server started", "[Warn] disk at 91%"]
1 -> WARN
2 -> CRITICAL
9 -> error: no level 9
300 Err(TryFromIntError(PosOverflow))