ExamplesBy LevelBy TopicLearning Paths
084 Intermediate

084 — From and Into Traits

Functional Programming

Tutorial

The Problem

Implement Rust's From and TryFrom traits for type conversions: bidirectional temperature conversion between Celsius and Fahrenheit, parsing a Color from &str with TryFrom, and validating a raw user record into a typed User. Compare with OCaml's explicit named conversion functions.

🎯 Learning Outcomes

  • • Implement From<A> for B and gain Into<B> for A automatically
  • • Use TryFrom<&str> when conversion can fail, returning Result<Self, Self::Error>
  • • Chain map_err and ? in TryFrom implementations
  • • Understand the blanket impl: impl<T, U: From<T>> Into<T> for U
  • • Use .into() at call sites for ergonomic type conversion
  • • Map Rust's trait-based conversion system to OCaml's explicit function naming
  • Code Example

    #![allow(clippy::all)]
    // 084: From and Into Traits
    
    // Approach 1: Temperature conversion
    #[derive(Debug, Clone, Copy)]
    struct Celsius(f64);
    
    #[derive(Debug, Clone, Copy)]
    struct Fahrenheit(f64);
    
    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)
        }
    }
    
    // Approach 2: Enum from string (TryFrom)
    #[derive(Debug, PartialEq)]
    enum Color {
        Red,
        Green,
        Blue,
    }
    
    impl TryFrom<&str> for Color {
        type Error = String;
        fn try_from(s: &str) -> Result<Self, Self::Error> {
            match s {
                "red" => Ok(Color::Red),
                "green" => Ok(Color::Green),
                "blue" => Ok(Color::Blue),
                _ => Err(format!("Unknown color: {}", s)),
            }
        }
    }
    
    impl From<Color> for &str {
        fn from(c: Color) -> Self {
            match c {
                Color::Red => "red",
                Color::Green => "green",
                Color::Blue => "blue",
            }
        }
    }
    
    // Approach 3: From for complex types
    struct RawUser {
        name: String,
        age: String,
        email: String,
    }
    
    #[derive(Debug, PartialEq)]
    struct User {
        name: String,
        age: u32,
        email: String,
    }
    
    impl TryFrom<RawUser> for User {
        type Error = String;
        fn try_from(raw: RawUser) -> Result<Self, Self::Error> {
            let age = raw.age.parse().map_err(|_| "Invalid age".to_string())?;
            Ok(User {
                name: raw.name,
                age,
                email: raw.email,
            })
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_celsius_to_fahrenheit() {
            let f: Fahrenheit = Celsius(100.0).into();
            assert!((f.0 - 212.0).abs() < 0.001);
        }
    
        #[test]
        fn test_fahrenheit_to_celsius() {
            let c: Celsius = Fahrenheit(32.0).into();
            assert!(c.0.abs() < 0.001);
        }
    
        #[test]
        fn test_color_try_from() {
            assert_eq!(Color::try_from("red"), Ok(Color::Red));
            assert!(Color::try_from("purple").is_err());
        }
    
        #[test]
        fn test_user_try_from() {
            let raw = RawUser {
                name: "Alice".into(),
                age: "30".into(),
                email: "a@b.com".into(),
            };
            let user = User::try_from(raw).unwrap();
            assert_eq!(user.age, 30);
        }
    
        #[test]
        fn test_user_invalid() {
            let raw = RawUser {
                name: "Bob".into(),
                age: "xyz".into(),
                email: "b@c.com".into(),
            };
            assert!(User::try_from(raw).is_err());
        }
    }

    Key Differences

    AspectRustOCaml
    Infallibleimpl From<A> for Bb_of_a : a -> b function
    Fallibleimpl TryFrom<A> for Bb_of_a : a -> ('b, err) result
    Ergonomics.into() callExplicit function call
    Generic over conversionTrait bound From<A>Higher-order function parameter
    Auto-blanketInto from FromManual
    Code reuseOne From impl for all call sitesOne function, explicit at each call

    The From/Into system is one of Rust's most pervasive patterns. Standard library types extensively use it: String::from("hello"), Vec::from([1, 2, 3]), error propagation with ?. Implementing From for your types integrates them into this ecosystem.

    OCaml Approach

    OCaml uses plain functions: fahrenheit_of_celsius, celsius_of_fahrenheit, color_of_string, user_of_raw. There is no trait system to unify these under a single interface. Code that needs to be generic over conversions must take the conversion function as a parameter. The result type mirrors Rust: Ok and Error are standard OCaml result constructors, making Result.bind chains natural.

    Full Source

    #![allow(clippy::all)]
    // 084: From and Into Traits
    
    // Approach 1: Temperature conversion
    #[derive(Debug, Clone, Copy)]
    struct Celsius(f64);
    
    #[derive(Debug, Clone, Copy)]
    struct Fahrenheit(f64);
    
    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)
        }
    }
    
    // Approach 2: Enum from string (TryFrom)
    #[derive(Debug, PartialEq)]
    enum Color {
        Red,
        Green,
        Blue,
    }
    
    impl TryFrom<&str> for Color {
        type Error = String;
        fn try_from(s: &str) -> Result<Self, Self::Error> {
            match s {
                "red" => Ok(Color::Red),
                "green" => Ok(Color::Green),
                "blue" => Ok(Color::Blue),
                _ => Err(format!("Unknown color: {}", s)),
            }
        }
    }
    
    impl From<Color> for &str {
        fn from(c: Color) -> Self {
            match c {
                Color::Red => "red",
                Color::Green => "green",
                Color::Blue => "blue",
            }
        }
    }
    
    // Approach 3: From for complex types
    struct RawUser {
        name: String,
        age: String,
        email: String,
    }
    
    #[derive(Debug, PartialEq)]
    struct User {
        name: String,
        age: u32,
        email: String,
    }
    
    impl TryFrom<RawUser> for User {
        type Error = String;
        fn try_from(raw: RawUser) -> Result<Self, Self::Error> {
            let age = raw.age.parse().map_err(|_| "Invalid age".to_string())?;
            Ok(User {
                name: raw.name,
                age,
                email: raw.email,
            })
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_celsius_to_fahrenheit() {
            let f: Fahrenheit = Celsius(100.0).into();
            assert!((f.0 - 212.0).abs() < 0.001);
        }
    
        #[test]
        fn test_fahrenheit_to_celsius() {
            let c: Celsius = Fahrenheit(32.0).into();
            assert!(c.0.abs() < 0.001);
        }
    
        #[test]
        fn test_color_try_from() {
            assert_eq!(Color::try_from("red"), Ok(Color::Red));
            assert!(Color::try_from("purple").is_err());
        }
    
        #[test]
        fn test_user_try_from() {
            let raw = RawUser {
                name: "Alice".into(),
                age: "30".into(),
                email: "a@b.com".into(),
            };
            let user = User::try_from(raw).unwrap();
            assert_eq!(user.age, 30);
        }
    
        #[test]
        fn test_user_invalid() {
            let raw = RawUser {
                name: "Bob".into(),
                age: "xyz".into(),
                email: "b@c.com".into(),
            };
            assert!(User::try_from(raw).is_err());
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_celsius_to_fahrenheit() {
            let f: Fahrenheit = Celsius(100.0).into();
            assert!((f.0 - 212.0).abs() < 0.001);
        }
    
        #[test]
        fn test_fahrenheit_to_celsius() {
            let c: Celsius = Fahrenheit(32.0).into();
            assert!(c.0.abs() < 0.001);
        }
    
        #[test]
        fn test_color_try_from() {
            assert_eq!(Color::try_from("red"), Ok(Color::Red));
            assert!(Color::try_from("purple").is_err());
        }
    
        #[test]
        fn test_user_try_from() {
            let raw = RawUser {
                name: "Alice".into(),
                age: "30".into(),
                email: "a@b.com".into(),
            };
            let user = User::try_from(raw).unwrap();
            assert_eq!(user.age, 30);
        }
    
        #[test]
        fn test_user_invalid() {
            let raw = RawUser {
                name: "Bob".into(),
                age: "xyz".into(),
                email: "b@c.com".into(),
            };
            assert!(User::try_from(raw).is_err());
        }
    }

    Deep Comparison

    Core Insight

    From<T> defines how to create a type from T. Into<T> is the reverse view. Implementing From auto-provides Into. This replaces ad-hoc conversion functions with a unified protocol.

    OCaml Approach

  • • Manual conversion functions: int_of_float, string_of_int
  • • No unified conversion trait
  • • Module-level of_* / to_* conventions
  • Rust Approach

  • impl From<Source> for Target
  • Into comes free via blanket impl
  • .into() for ergonomic conversion
  • TryFrom/TryInto for fallible conversions
  • Comparison Table

    FeatureOCamlRust
    Conversionof_string, to_int functionsFrom/Into traits
    InfallibleManual functionFrom/Into
    FallibleReturn option/resultTryFrom/TryInto
    AutoNoInto from From

    Exercises

  • Add impl From<i32> for Color that maps 0 → Red, 1 → Green, 2 → Blue and panics otherwise. Then add TryFrom<i32> that returns Err instead of panicking.
  • Implement From<Vec<(String, i32)>> for a HashMap<String, i32>.
  • Write a Validated<T> newtype that wraps T and implement TryFrom<String> for Validated<Email> where Email is another newtype.
  • Create a conversion chain: RawConfigParsedConfigValidatedConfig, each step using TryFrom.
  • In OCaml, write a convert functor Convert(S : sig type t end)(D : sig type t val of_s : S.t -> D.t end) and show how it compares to Rust's impl From<S> for D.
  • Open Source Repos