310: Infallible Conversions
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
TryFrom<T> as the fallible counterpart to From<T>TryFrom<u32> for a newtype wrapper with a validity constrainttry_into() for conversions that can fail with a specific error typeFrom implies Into; TryFrom implies TryIntoCode 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
TryFrom/TryInto provide a standard interface for all fallible conversions; OCaml uses ad-hoc *_of_* naming conventions.impl TryFrom<A> for B automatically provides impl TryInto<B> for A — symmetry is built in.TryFrom<u64> for u8, u16, etc. — the idiomatic way to do checked narrowing.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);
}
}#[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
BoundedI32(i32) newtype that wraps integers in range [MIN, MAX], implementing TryFrom<i32> with a RangeError for out-of-range values.TryFrom<i64> for i32 to safely narrow a value, handling the error case explicitly.TryFrom<&str> for a Color enum with Red, Green, Blue variants, parsing case-insensitively.