16.4 Fallible Conversions: TryFrom and TryInto
When a conversion might fail (e.g., due to potential data loss, invalid input values, or unmet invariants), Rust employs the TryFrom<T> and TryInto<U> traits. These methods return a Result<TargetType, ErrorType>, explicitly forcing the caller to handle the possibility of conversion failure.
impl TryFrom<T> for Udefines a conversion fromTtoUthat might fail, returningOk(U)on success orErr(ErrorType)on failure.- If
TryFrom<T>is implemented forU, the compiler automatically providesTryInto<U>forT(similar to theFrom/Intorelationship).
16.4.1 Standard Library Examples
Converting between numeric types where the target type has a narrower range is a prime use case:
use std::convert::{TryFrom, TryInto}; // Must import the traits
fn main() {
let large_value: i32 = 1000;
let small_value: i32 = 50;
let negative_value: i32 = -10;
// Try converting i32 to u8 (valid range 0-255)
match u8::try_from(large_value) {
Ok(v) => println!("{} converted to u8: {}", large_value, v),
// This arm won't execute
Err(e) => println!("Failed to convert {} to u8: {}", large_value, e),
// Error: out of range
}
match u8::try_from(small_value) {
Ok(v) => println!("{} converted to u8: {}", small_value, v), // Success: 50
Err(e) => println!("Failed to convert {} to u8: {}", small_value, e),
}
// Using try_into() often requires type annotation if not inferable
let result: Result<u8, _> = negative_value.try_into();
// Inferred error type std::num::TryFromIntError
match result {
Ok(v) => println!("{} converted to u8: {}", negative_value, v),
Err(e) => println!("Failed to convert {} to u8: {}", negative_value, e),
// Error: out of range (negative)
}
}
The specific error type (like std::num::TryFromIntError for standard numeric conversions) provides context about the failure.
16.4.2 Implementing TryFrom for Custom Types
Implement TryFrom to handle conversions that involve validation or potential failure for your types:
use std::convert::{TryFrom, TryInto};
use std::num::TryFromIntError; // Error type for standard int conversion failures
// A type representing a percentage (0-100)
#[derive(Debug, PartialEq)]
struct Percentage(u8);
#[derive(Debug, PartialEq)]
enum PercentageError {
OutOfRange,
ConversionFailed(TryFromIntError), // Wrap the underlying error if needed
}
// Allow conversion from i32, failing if outside 0-100 range
impl TryFrom<i32> for Percentage {
type Error = PercentageError; // Associated error type for this conversion
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value < 0 || value > 100 {
Err(PercentageError::OutOfRange)
} else {
// We know value is in 0..=100.
// We could use `value as u8`, but using u8::try_from is safer
// in case the logic had a flaw, and it handles potential (though
// unlikely here) intermediate conversion issues.
match u8::try_from(value) {
Ok(val_u8) => Ok(Percentage(val_u8)),
Err(e) => Err(PercentageError::ConversionFailed(e)),
// This branch is unreachable if the 0..=100 check is correct.
}
// Simpler alternative, given the check: Ok(Percentage(value as u8))
}
}
}
fn main() {
assert_eq!(Percentage::try_from(50), Ok(Percentage(50)));
assert_eq!(Percentage::try_from(100), Ok(Percentage(100)));
assert_eq!(Percentage::try_from(101), Err(PercentageError::OutOfRange));
assert_eq!(Percentage::try_from(-1), Err(PercentageError::OutOfRange));
// Using try_into()
let p_result: Result<Percentage, _> = 75i32.try_into();
assert_eq!(p_result, Ok(Percentage(75)));
let p_fail: Result<Percentage, _> = (-5i32).try_into();
assert_eq!(p_fail, Err(PercentageError::OutOfRange));
}
Using TryFrom/TryInto leads to more robust code by making potential conversion failures explicit and requiring error handling.