Step 1 of 7
From and Into
Converting one type into another is so common that the standard library has traits for it. impl From<A> for B says "a B can always be made from an A". The conversion can't fail, so it returns a plain B, not a Result.
You've used From many times already: String::from("hi") is From<&str> for String, and in the custom errors module you wrote From impls so ? could convert errors. Here it is for your own units:
#[derive(Debug, Clone, Copy)]
struct Celsius(f64);
#[derive(Debug, Clone, Copy)]
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 body = Celsius(37.0);
let f = Fahrenheit::from(body);
println!("{:.1}°C is {:.1}°F", body.0, f.0);
let boiling: Fahrenheit = Celsius(100.0).into();
println!("water boils at {:.1}°F", boiling.0);
}
37.0°C is 98.6°F
water boils at 212.0°F
impl From<Celsius> for Fahrenheithas one method,fn from(c: Celsius) -> Self. Inside a trait impl,Selfis the type being implemented (hereFahrenheit).Fahrenheit::from(body)calls it directly..into()is the same conversion seen from the other side. You never implementIntoyourself: the standard library gives every typeInto<B>for free as soon asB: From<A>. Always implementFrom(clippy warns withfrom_over_intoif you implementIntodirectly)..into()has to know the target type, so it usually needs an annotation likelet boiling: Fahrenheit, or a function parameter type to aim at.
Into in parameters: friendly APIs
A very common pattern at work: accept impl Into<String> so callers can pass either a &str or a String:
struct User {
name: String,
}
impl User {
fn new(name: impl Into<String>) -> Self {
User { name: name.into() }
}
}
fn main() {
let a = User::new("ana");
let b = User::new(String::from("bo"));
println!("{} {}", a.name, b.name);
}
ana bo
With a &str the function allocates a new String; with a String it just moves it in, no copy. Callers don't have to write .to_string() everywhere.
When From is the right trait
From promises a conversion that always works and is obvious: there is only one sensible answer. Celsius to Fahrenheit, u8 to u32, &str to String. If a conversion can fail (a number that might be out of range), use TryFrom (next step). If it loses information or there are several reasonable answers (seconds to minutes: round down? up?), write a normal method with a clear name instead.
Common mistakes
.into()without a target type:
struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 1.8 + 32.0)
}
}
fn main() {
let f = Celsius(100.0).into();
println!("{}", f.0);
}
error[E0282]: type annotations needed
Many types could be made from a Celsius, so Rust can't guess. Write let f: Fahrenheit = ....
- Implementing
Fromin the wrong direction.impl From<Celsius> for Fahrenheitlets you turn Celsius into Fahrenheit, not the other way. Each direction is its own impl.
Your task
One foot is exactly 0.3048 meters. Implement From<Feet> for Meters and From<Meters> for Feet, so both Meters::from(Feet(10.0)) and let f: Feet = Meters(3.048).into(); work.
Challenge 2
Colors are often stored as one number like 0xff8800 (red ff, green 88, blue 00). Implement From<u32> for Color and From<Color> for u32.
To take the number apart, n >> 16 shifts it right by 16 bits (2 hex digits = 8 bits each), and & 0xff keeps only the lowest 8 bits: red is (n >> 16) & 0xff, green (n >> 8) & 0xff, blue n & 0xff. Going back, (r as u32) << 16 shifts left, and | combines the parts. Ignore anything above the lowest 24 bits.
Challenge 3
Usernames are stored trimmed and in lowercase. Write a newtype struct Username(String) (with #[derive(Debug, PartialEq)]) and implement both From<&str> and From<String> for it, each trimming spaces and lowercasing. Then write fn welcome(user: impl Into<Username>) -> String that returns welcome, <name>, so welcome(" Ana ") and welcome(String::from("BO")) both work.