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 U
defines a conversion fromT
toU
that might fail, returningOk(U)
on success orErr(ErrorType)
on failure.- If
TryFrom<T>
is implemented forU
, the compiler automatically providesTryInto<U>
forT
.
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, so 'as u8' is safe here. // Alternatively, use u8::try_from for maximum safety, mapping the error. match u8::try_from(value) { Ok(val_u8) => Ok(Percentage(val_u8)), Err(e) => Err(PercentageError::ConversionFailed(e)), // Should not happen if range check is correct } // Simpler, 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.