RustArena

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

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

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 = ....

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.

Next: TryFrom and TryInto