ExamplesBy LevelBy TopicLearning Paths
310 Intermediate

310: Infallible Conversions

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "310: Infallible Conversions" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Some conversions are always valid (`u8` to `u32`), while others might fail (`u32` to `u8` — might overflow). Key difference from OCaml: 1. **Trait unification**: `TryFrom`/`TryInto` provide a standard interface for all fallible conversions; OCaml uses ad

Tutorial

The Problem

Some conversions are always valid (u8 to u32), while others might fail (u32 to u8 — might overflow). Rust encodes this distinction in the type system: From/Into for infallible conversions, TryFrom/TryInto for fallible ones. Using TryFrom for a conversion that might fail makes failure handling explicit and visible, unlike C-style implicit narrowing casts that silently truncate. This mirrors OCaml's explicit type coercions.

🎯 Learning Outcomes

  • • Understand TryFrom<T> as the fallible counterpart to From<T>
  • • Implement TryFrom<u32> for a newtype wrapper with a validity constraint
  • • Use try_into() for conversions that can fail with a specific error type
  • • Recognize the relationship: infallible From implies Into; TryFrom implies TryInto
  • Code Example

    #![allow(clippy::all)]
    //! # Infallible Conversions with Into
    //!
    //! `Infallible` marks conversions that cannot fail.
    
    use std::convert::TryFrom;
    
    /// Non-zero wrapper with fallible construction
    #[derive(Debug, Clone, Copy, PartialEq)]
    pub struct NonZeroU32(u32);
    
    #[derive(Debug, PartialEq)]
    pub struct ZeroError;
    
    impl TryFrom<u32> for NonZeroU32 {
        type Error = ZeroError;
        fn try_from(n: u32) -> Result<Self, ZeroError> {
            if n == 0 {
                Err(ZeroError)
            } else {
                Ok(NonZeroU32(n))
            }
        }
    }
    
    impl NonZeroU32 {
        pub fn get(&self) -> u32 {
            self.0
        }
    }
    
    /// Newtype with infallible From conversion
    #[derive(Debug, PartialEq)]
    pub struct Meters(pub f64);
    
    impl From<f64> for Meters {
        fn from(v: f64) -> Self {
            Meters(v)
        }
    }
    
    /// Demonstrate infallible conversions
    pub fn u8_to_u32(small: u8) -> u32 {
        u32::from(small) // always succeeds
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_nonzero_ok() {
            assert_eq!(NonZeroU32::try_from(5), Ok(NonZeroU32(5)));
        }
    
        #[test]
        fn test_nonzero_err() {
            assert_eq!(NonZeroU32::try_from(0), Err(ZeroError));
        }
    
        #[test]
        fn test_from_infallible() {
            assert_eq!(u8_to_u32(255), 255);
        }
    
        #[test]
        fn test_meters_from() {
            let m: Meters = Meters::from(5.0);
            assert_eq!(m, Meters(5.0));
        }
    
        #[test]
        fn test_into() {
            let m: Meters = 100.0f64.into();
            assert_eq!(m.0, 100.0);
        }
    }

    Key Differences

  • Trait unification: TryFrom/TryInto provide a standard interface for all fallible conversions; OCaml uses ad-hoc *_of_* naming conventions.
  • Symmetric pair: impl TryFrom<A> for B automatically provides impl TryInto<B> for A — symmetry is built in.
  • std numeric impls: The standard library implements TryFrom<u64> for u8, u16, etc. — the idiomatic way to do checked narrowing.
  • Orphan rule: You can implement TryFrom<ThirdPartyType> for your type but not for third-party types — same as From.
  • OCaml Approach

    OCaml uses explicit conversion functions — there is no standard TryFrom trait equivalent:

    type non_zero = NonZero of int
    
    exception ZeroError
    
    let non_zero_of_int n =
      if n = 0 then Error `ZeroError
      else Ok (NonZero n)
    

    OCaml's lack of numeric implicit conversion makes this less critical — explicit function calls are always required.

    Full Source

    #![allow(clippy::all)]
    //! # Infallible Conversions with Into
    //!
    //! `Infallible` marks conversions that cannot fail.
    
    use std::convert::TryFrom;
    
    /// Non-zero wrapper with fallible construction
    #[derive(Debug, Clone, Copy, PartialEq)]
    pub struct NonZeroU32(u32);
    
    #[derive(Debug, PartialEq)]
    pub struct ZeroError;
    
    impl TryFrom<u32> for NonZeroU32 {
        type Error = ZeroError;
        fn try_from(n: u32) -> Result<Self, ZeroError> {
            if n == 0 {
                Err(ZeroError)
            } else {
                Ok(NonZeroU32(n))
            }
        }
    }
    
    impl NonZeroU32 {
        pub fn get(&self) -> u32 {
            self.0
        }
    }
    
    /// Newtype with infallible From conversion
    #[derive(Debug, PartialEq)]
    pub struct Meters(pub f64);
    
    impl From<f64> for Meters {
        fn from(v: f64) -> Self {
            Meters(v)
        }
    }
    
    /// Demonstrate infallible conversions
    pub fn u8_to_u32(small: u8) -> u32 {
        u32::from(small) // always succeeds
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_nonzero_ok() {
            assert_eq!(NonZeroU32::try_from(5), Ok(NonZeroU32(5)));
        }
    
        #[test]
        fn test_nonzero_err() {
            assert_eq!(NonZeroU32::try_from(0), Err(ZeroError));
        }
    
        #[test]
        fn test_from_infallible() {
            assert_eq!(u8_to_u32(255), 255);
        }
    
        #[test]
        fn test_meters_from() {
            let m: Meters = Meters::from(5.0);
            assert_eq!(m, Meters(5.0));
        }
    
        #[test]
        fn test_into() {
            let m: Meters = 100.0f64.into();
            assert_eq!(m.0, 100.0);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_nonzero_ok() {
            assert_eq!(NonZeroU32::try_from(5), Ok(NonZeroU32(5)));
        }
    
        #[test]
        fn test_nonzero_err() {
            assert_eq!(NonZeroU32::try_from(0), Err(ZeroError));
        }
    
        #[test]
        fn test_from_infallible() {
            assert_eq!(u8_to_u32(255), 255);
        }
    
        #[test]
        fn test_meters_from() {
            let m: Meters = Meters::from(5.0);
            assert_eq!(m, Meters(5.0));
        }
    
        #[test]
        fn test_into() {
            let m: Meters = 100.0f64.into();
            assert_eq!(m.0, 100.0);
        }
    }

    Deep Comparison

    infallible-conversions

    See README.md for details.

    Exercises

  • Implement a BoundedI32(i32) newtype that wraps integers in range [MIN, MAX], implementing TryFrom<i32> with a RangeError for out-of-range values.
  • Use the standard library's TryFrom<i64> for i32 to safely narrow a value, handling the error case explicitly.
  • Implement TryFrom<&str> for a Color enum with Red, Green, Blue variants, parsing case-insensitively.
  • Open Source Repos