RustArena

Step 2 of 7

TryFrom and TryInto

Some conversions can fail. An i32 might be a valid percentage (0 to 100) or not. For those, implement TryFrom: it returns a Result, so the caller has to deal with the failure.

#[derive(Debug)]
struct Percent(u8);

impl TryFrom<i32> for Percent {
    type Error = String;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if (0..=100).contains(&value) {
            Ok(Percent(value as u8))
        } else {
            Err(format!("{value} is not a percentage"))
        }
    }
}

fn main() {
    println!("{:?}", Percent::try_from(42));
    println!("{:?}", Percent::try_from(150));

    let p: Result<Percent, _> = 7.try_into();
    if let Ok(percent) = p {
        println!("{}%", percent.0);
    }
}
Ok(Percent(42))
Err("150 is not a percentage")
7%

Piece by piece:

Safer than as

The standard library implements TryFrom between all the integer types. That makes it the safe replacement for as, which silently chops numbers that don't fit:

fn main() {
    let big: i32 = 300;
    println!("{}", big as u8);
    println!("{:?}", u8::try_from(big));
    println!("{:?}", u8::try_from(200_i32));
}
44
Err(TryFromIntError(PosOverflow))
Ok(200)

300 as u8 keeps only the lowest 8 bits and gives 44: a bug waiting to happen. Code reviewers will often ask you to replace an as with try_from when the value could be out of range. Combined with ? from the errors module, it's one short line:

let quantity: u8 = u8::try_from(input).map_err(|_| "quantity too large")?;

Common mistakes

Your task

Implement TryFrom<u8> for Weekday, where 1 is Monday and 7 is Sunday. Any other number is an error with the message invalid day: <n> (use type Error = String;).

Challenge 2

Product reviews are 1 to 5 stars. Implement TryFrom<i32> for Stars that returns Err(StarsError::TooLow) below 1, Err(StarsError::TooHigh) above 5, and Ok(Stars(n)) otherwise. This time the error type is your own enum.

Challenge 3

A warehouse API receives quantities as i64, but the storage system keeps them as u8. Write fn to_quantities(values: &[i64]) -> Result<Vec<u8>, String> that converts every value with u8::try_from. On the first value that doesn't fit, return Err with the message value <v> doesn't fit in a u8. Use ? so the error stops the loop.

Previous: From and Into Next: FromStr: parse into your own type