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:
type Error = String;is an associated type: a type the impl chooses and the trait refers to asSelf::Error. EachTryFromimpl picks its own error type: aString, your own error enum, anything. (The advanced traits module covers associated types in depth.)fn try_from(value: i32) -> Result<Self, Self::Error>does the check and returnsOkorErr.- Just like
Fromgives youInto,TryFromgives youTryIntofor free:7.try_into(), again with the target type written somewhere. - Inside,
value as u8is safe because we just checked the range.
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
- Forgetting
type Error.TryFromhas two parts to fill in: the type and the method. Leaving out the type giveserror[E0046]: not all trait items implemented, missing: `Error`. - Returning
Selfinstead ofResult<Self, Self::Error>. The signature must match the trait exactly. - Using
TryFromfor conversions that can't fail. If every input is valid, implementFrom: callers then don't have to handle an error that can never happen.
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