ExamplesBy LevelBy TopicLearning Paths
404 Intermediate

404: From, Into, TryFrom, TryInto

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "404: From, Into, TryFrom, TryInto" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Type conversions are pervasive: temperatures between Celsius and Fahrenheit, integers between types, domain values from raw data. Key difference from OCaml: 1. **Blanket impl**: Rust's `Into` is automatically derived from `From`; OCaml requires implementing each conversion direction independently.

Tutorial

The Problem

Type conversions are pervasive: temperatures between Celsius and Fahrenheit, integers between types, domain values from raw data. Ad-hoc conversion functions (celsius_to_fahrenheit, as_kelvin) don't compose and require memorizing function names. The From/Into trait pair standardizes infallible conversions: implement From<A> for B and get Into<A> on B for free via blanket impl. TryFrom/TryInto handle fallible conversions returning Result. This unification means all conversions use .into(), .from(), or .try_into() consistently.

From/Into power the entire ? operator error conversion, Into<Vec<u8>> in network APIs, From<String> for PathBuf, and virtually every constructor pattern in std.

🎯 Learning Outcomes

  • • Understand the From/Into blanket impl relationship: implementing From<A> for B gives Into<B> for A automatically
  • • Learn TryFrom/TryInto for fallible conversions with type Error
  • • See how temperature unit conversions demonstrate clean From chains
  • • Understand why .into() sometimes requires type annotation to resolve ambiguity
  • • Learn how From powers the ? operator's error type conversion
  • Code Example

    use std::convert::TryFrom;
    
    struct PositiveInt(u32);
    
    impl TryFrom<i32> for PositiveInt {
        type Error = &'static str;
    
        fn try_from(n: i32) -> Result<Self, Self::Error> {
            if n > 0 {
                Ok(PositiveInt(n as u32))
            } else {
                Err("must be positive")
            }
        }
    }
    
    fn main() {
        match PositiveInt::try_from(42) {
            Ok(p) => println!("Got: {}", p.0),
            Err(e) => println!("Error: {}", e),
        }
    }

    Key Differences

  • Blanket impl: Rust's Into is automatically derived from From; OCaml requires implementing each conversion direction independently.
  • Error conversion: Rust's ? operator uses From<E1> for E2 for automatic error coercion; OCaml uses Result.map_error explicitly.
  • Fallible variants: Rust has separate TryFrom/TryInto for fallible conversions with type Error; OCaml uses option or result as return types on any function.
  • Type inference: Rust sometimes needs type annotation with .into() (let f: Fahrenheit = c.into()); OCaml's explicit function names make the target type clear.
  • OCaml Approach

    OCaml uses explicit conversion functions in modules: Celsius.of_fahrenheit, Temperature.to_kelvin. There is no equivalent of the From/Into blanket relationship — each conversion function is independent. OCaml's Result.bind and let* syntax handle fallible conversions. The ppx_conv_func library provides some standardization but OCaml has no universal conversion trait.

    Full Source

    #![allow(clippy::all)]
    //! From, Into, TryFrom, TryInto Traits
    //!
    //! Type conversion traits for infallible and fallible conversions.
    
    use std::fmt;
    
    /// Temperature in Celsius.
    #[derive(Debug, Clone, PartialEq)]
    pub struct Celsius(pub f64);
    
    /// Temperature in Fahrenheit.
    #[derive(Debug, Clone, PartialEq)]
    pub struct Fahrenheit(pub f64);
    
    /// Kelvin temperature.
    #[derive(Debug, Clone, PartialEq)]
    pub struct Kelvin(pub f64);
    
    // Infallible conversions using From
    
    impl From<Celsius> for Fahrenheit {
        fn from(c: Celsius) -> Self {
            Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
        }
    }
    
    impl From<Fahrenheit> for Celsius {
        fn from(f: Fahrenheit) -> Self {
            Celsius((f.0 - 32.0) * 5.0 / 9.0)
        }
    }
    
    impl From<Celsius> for Kelvin {
        fn from(c: Celsius) -> Self {
            Kelvin(c.0 + 273.15)
        }
    }
    
    impl From<Kelvin> for Celsius {
        fn from(k: Kelvin) -> Self {
            Celsius(k.0 - 273.15)
        }
    }
    
    /// A validated positive integer.
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct PositiveInt(u32);
    
    impl PositiveInt {
        /// Returns the inner value.
        pub fn value(&self) -> u32 {
            self.0
        }
    }
    
    /// Error for non-positive values.
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct NotPositiveError;
    
    impl fmt::Display for NotPositiveError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "value must be positive (> 0)")
        }
    }
    
    impl std::error::Error for NotPositiveError {}
    
    // Fallible conversion using TryFrom
    
    impl TryFrom<i32> for PositiveInt {
        type Error = NotPositiveError;
    
        fn try_from(n: i32) -> Result<Self, Self::Error> {
            if n > 0 {
                Ok(PositiveInt(n as u32))
            } else {
                Err(NotPositiveError)
            }
        }
    }
    
    impl TryFrom<i64> for PositiveInt {
        type Error = NotPositiveError;
    
        fn try_from(n: i64) -> Result<Self, Self::Error> {
            if n > 0 && n <= u32::MAX as i64 {
                Ok(PositiveInt(n as u32))
            } else {
                Err(NotPositiveError)
            }
        }
    }
    
    /// A valid network port (1024-65535 for non-privileged).
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Port(u16);
    
    impl Port {
        /// Returns the port number.
        pub fn number(&self) -> u16 {
            self.0
        }
    }
    
    /// Error for invalid port values.
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub enum PortError {
        ParseError(String),
        TooLow(u16),
    }
    
    impl fmt::Display for PortError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match self {
                PortError::ParseError(s) => write!(f, "invalid port: {}", s),
                PortError::TooLow(n) => write!(f, "port {} is below 1024", n),
            }
        }
    }
    
    impl TryFrom<&str> for Port {
        type Error = PortError;
    
        fn try_from(s: &str) -> Result<Self, Self::Error> {
            let n: u16 = s
                .parse()
                .map_err(|_| PortError::ParseError(s.to_string()))?;
            if n >= 1024 {
                Ok(Port(n))
            } else {
                Err(PortError::TooLow(n))
            }
        }
    }
    
    impl TryFrom<u32> for Port {
        type Error = PortError;
    
        fn try_from(n: u32) -> Result<Self, Self::Error> {
            if (1024..=65535).contains(&n) {
                Ok(Port(n as u16))
            } else if n < 1024 {
                Err(PortError::TooLow(n as u16))
            } else {
                Err(PortError::ParseError(format!("{} exceeds u16", n)))
            }
        }
    }
    
    /// Demonstrates the `as` keyword for primitive casts.
    pub fn primitive_casts() -> (i32, u8, f64) {
        let a: i64 = 1000;
        let b: i32 = a as i32; // truncating cast
    
        let c: i32 = 300;
        let d: u8 = c as u8; // wrapping cast (300 % 256 = 44)
    
        let e: i32 = 42;
        let f: f64 = e as f64; // widening cast
    
        (b, d, f)
    }
    
    /// Generic function accepting anything convertible to String.
    pub fn greet<S: Into<String>>(name: S) {
        let name = name.into();
        println!("Hello, {}!", name);
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_celsius_to_fahrenheit() {
            let c = Celsius(0.0);
            let f: Fahrenheit = c.into();
            assert!((f.0 - 32.0).abs() < 0.001);
        }
    
        #[test]
        fn test_fahrenheit_to_celsius() {
            let f = Fahrenheit(212.0);
            let c = Celsius::from(f);
            assert!((c.0 - 100.0).abs() < 0.001);
        }
    
        #[test]
        fn test_celsius_to_kelvin() {
            let c = Celsius(0.0);
            let k: Kelvin = c.into();
            assert!((k.0 - 273.15).abs() < 0.001);
        }
    
        #[test]
        fn test_kelvin_to_celsius() {
            let k = Kelvin(0.0);
            let c: Celsius = k.into();
            assert!((c.0 - (-273.15)).abs() < 0.001);
        }
    
        #[test]
        fn test_positive_int_success() {
            let p: Result<PositiveInt, _> = 42i32.try_into();
            assert!(p.is_ok());
            assert_eq!(p.unwrap().value(), 42);
        }
    
        #[test]
        fn test_positive_int_zero_fails() {
            let p: Result<PositiveInt, _> = 0i32.try_into();
            assert!(p.is_err());
        }
    
        #[test]
        fn test_positive_int_negative_fails() {
            let p: Result<PositiveInt, _> = (-5i32).try_into();
            assert!(p.is_err());
        }
    
        #[test]
        fn test_port_valid() {
            let p = Port::try_from("8080");
            assert!(p.is_ok());
            assert_eq!(p.unwrap().number(), 8080);
        }
    
        #[test]
        fn test_port_below_1024() {
            let p = Port::try_from("80");
            assert!(matches!(p, Err(PortError::TooLow(80))));
        }
    
        #[test]
        fn test_port_invalid_string() {
            let p = Port::try_from("abc");
            assert!(matches!(p, Err(PortError::ParseError(_))));
        }
    
        #[test]
        fn test_port_from_u32() {
            let p = Port::try_from(3000u32);
            assert!(p.is_ok());
            assert_eq!(p.unwrap().number(), 3000);
        }
    
        #[test]
        fn test_primitive_casts() {
            let (b, d, f) = primitive_casts();
            assert_eq!(b, 1000);
            assert_eq!(d, 44); // 300 % 256
            assert_eq!(f, 42.0);
        }
    
        #[test]
        fn test_stdlib_try_from() {
            // Standard library TryFrom for primitive narrowing
            let ok: Result<u8, _> = u8::try_from(100i32);
            assert_eq!(ok, Ok(100u8));
    
            let err: Result<u8, _> = u8::try_from(1000i32);
            assert!(err.is_err());
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_celsius_to_fahrenheit() {
            let c = Celsius(0.0);
            let f: Fahrenheit = c.into();
            assert!((f.0 - 32.0).abs() < 0.001);
        }
    
        #[test]
        fn test_fahrenheit_to_celsius() {
            let f = Fahrenheit(212.0);
            let c = Celsius::from(f);
            assert!((c.0 - 100.0).abs() < 0.001);
        }
    
        #[test]
        fn test_celsius_to_kelvin() {
            let c = Celsius(0.0);
            let k: Kelvin = c.into();
            assert!((k.0 - 273.15).abs() < 0.001);
        }
    
        #[test]
        fn test_kelvin_to_celsius() {
            let k = Kelvin(0.0);
            let c: Celsius = k.into();
            assert!((c.0 - (-273.15)).abs() < 0.001);
        }
    
        #[test]
        fn test_positive_int_success() {
            let p: Result<PositiveInt, _> = 42i32.try_into();
            assert!(p.is_ok());
            assert_eq!(p.unwrap().value(), 42);
        }
    
        #[test]
        fn test_positive_int_zero_fails() {
            let p: Result<PositiveInt, _> = 0i32.try_into();
            assert!(p.is_err());
        }
    
        #[test]
        fn test_positive_int_negative_fails() {
            let p: Result<PositiveInt, _> = (-5i32).try_into();
            assert!(p.is_err());
        }
    
        #[test]
        fn test_port_valid() {
            let p = Port::try_from("8080");
            assert!(p.is_ok());
            assert_eq!(p.unwrap().number(), 8080);
        }
    
        #[test]
        fn test_port_below_1024() {
            let p = Port::try_from("80");
            assert!(matches!(p, Err(PortError::TooLow(80))));
        }
    
        #[test]
        fn test_port_invalid_string() {
            let p = Port::try_from("abc");
            assert!(matches!(p, Err(PortError::ParseError(_))));
        }
    
        #[test]
        fn test_port_from_u32() {
            let p = Port::try_from(3000u32);
            assert!(p.is_ok());
            assert_eq!(p.unwrap().number(), 3000);
        }
    
        #[test]
        fn test_primitive_casts() {
            let (b, d, f) = primitive_casts();
            assert_eq!(b, 1000);
            assert_eq!(d, 44); // 300 % 256
            assert_eq!(f, 42.0);
        }
    
        #[test]
        fn test_stdlib_try_from() {
            // Standard library TryFrom for primitive narrowing
            let ok: Result<u8, _> = u8::try_from(100i32);
            assert_eq!(ok, Ok(100u8));
    
            let err: Result<u8, _> = u8::try_from(1000i32);
            assert!(err.is_err());
        }
    }

    Deep Comparison

    OCaml vs Rust: From/Into Conversion Traits

    Side-by-Side Code

    OCaml — Module-based conversion

    module type TryFrom = sig
      type source
      type target
      type error
      val try_from : source -> (target, error) result
    end
    
    module StringToInt : TryFrom
      with type source = string
       and type target = int
       and type error = string = struct
      type source = string
      type target = int
      type error = string
      let try_from s =
        match int_of_string_opt s with
        | Some n -> Ok n
        | None -> Error ("Not a number: " ^ s)
    end
    
    let () =
      match StringToInt.try_from "42" with
      | Ok n -> Printf.printf "Got: %d\n" n
      | Error e -> Printf.printf "Error: %s\n" e
    

    Rust — Trait-based conversion

    use std::convert::TryFrom;
    
    struct PositiveInt(u32);
    
    impl TryFrom<i32> for PositiveInt {
        type Error = &'static str;
    
        fn try_from(n: i32) -> Result<Self, Self::Error> {
            if n > 0 {
                Ok(PositiveInt(n as u32))
            } else {
                Err("must be positive")
            }
        }
    }
    
    fn main() {
        match PositiveInt::try_from(42) {
            Ok(p) => println!("Got: {}", p.0),
            Err(e) => println!("Error: {}", e),
        }
    }
    

    Comparison Table

    AspectOCamlRust
    Infallible conversionFloat.of_int, Int.to_stringFrom trait, .into()
    Fallible conversion*_opt functions, custom modulesTryFrom trait, .try_into()
    Generic boundsModule functorsT: Into<U> trait bounds
    Automatic implNoneInto auto-derived from From
    Error typePart of resultAssociated type Error
    Primitive castsType-specific functionsas keyword

    From vs Into

    // Implementing From gives you Into for free
    impl From<Celsius> for Fahrenheit {
        fn from(c: Celsius) -> Fahrenheit {
            Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
        }
    }
    
    // Now both work:
    let f1 = Fahrenheit::from(Celsius(100.0));  // From
    let f2: Fahrenheit = Celsius(100.0).into();  // Into (auto-derived)
    

    The idiom: **implement From, use Into in bounds**.

    // Generic function accepting anything convertible
    fn process<T: Into<String>>(input: T) {
        let s: String = input.into();
        // ...
    }
    
    process("literal");        // &str -> String
    process(String::from("x")); // String -> String (no-op)
    

    TryFrom vs TryInto

    For fallible conversions:

    impl TryFrom<&str> for Port {
        type Error = PortError;
        fn try_from(s: &str) -> Result<Self, Self::Error> {
            let n: u16 = s.parse()?;
            if n >= 1024 { Ok(Port(n)) }
            else { Err(PortError::TooLow) }
        }
    }
    
    // Usage
    let p: Result<Port, _> = "8080".try_into();
    let p2 = Port::try_from("80");  // Err
    

    The as Keyword

    Rust's as is for primitive casts only:

    let a: i64 = 1000;
    let b: i32 = a as i32;  // truncating (safe here)
    
    let c: i32 = 300;
    let d: u8 = c as u8;    // wrapping: 300 % 256 = 44 ⚠️
    
    let e: f64 = 42i32 as f64;  // widening (safe)
    

    Warning: as can silently truncate! Use TryFrom when overflow matters.


    5 Takeaways

  • **Implement From, use Into in bounds.**
  • impl From<A> for B gives you impl Into<B> for A automatically.

  • **TryFrom/TryInto are for fallible conversions.**
  • Return Result with an associated error type.

  • OCaml uses modules; Rust uses traits.
  • Same concept, different implementation patterns.

  • **as is only for primitives and can lose data.**
  • It's fast but unchecked — use TryFrom for safety.

  • **Generic Into bounds make APIs flexible.**
  • Accept impl Into<String> to take &str, String, etc.

    Exercises

  • Color space conversion: Implement From<RgbColor> for HslColor and From<HslColor> for RgbColor using the standard formulas. Write a round-trip test verifying that converting RGB → HSL → RGB returns the original within floating-point tolerance.
  • Error conversion chain: Create three error types ParseError, IoError, AppError. Implement From<ParseError> for AppError and From<IoError> for AppError. Write a function using ? that combines a parse and an IO operation into a single Result<T, AppError>.
  • Integer hierarchy: Implement From<i32> for BigInt(Vec<i32>) and TryFrom<BigInt> for i32 (failing when the BigInt doesn't fit). Show that let big: BigInt = 42i32.into() works, and i32::try_from(big).unwrap_or(-1) handles overflow.
  • Open Source Repos