๐Ÿฆ€ Functional Rust
๐ŸŽฌ Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Traits define shared behaviour โ€” like interfaces but with default implementations

โ€ข Generics with trait bounds: fn process(item: T) โ€” monomorphized at compile time

โ€ข Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

โ€ข Blanket implementations apply traits to all types matching a bound

โ€ข Associated types and supertraits enable complex type relationships

084: From/Into Traits

Difficulty: 2 Level: Intermediate Implement `From<T>` to define infallible conversions between types โ€” and get `Into<T>` for free. Use `TryFrom`/`TryInto` when conversion can fail.

The Problem This Solves

Every codebase has conversions: Celsius to Fahrenheit, tuples to structs, raw strings to validated types. Without a standard vocabulary, everyone writes their own: `to_fahrenheit()`, `from_string()`, `as_point()` โ€” different names, inconsistent call sites, nothing interoperable. Rust's standard library defines the vocabulary: `From` and `Into` for infallible conversions, `TryFrom` and `TryInto` for fallible ones. Implement `From<Celsius> for Fahrenheit` and you get `Fahrenheit::from(celsius)` and `let f: Fahrenheit = celsius.into()` for free โ€” and any library function that accepts `impl Into<Fahrenheit>` will accept your `Celsius` automatically. This is the type conversion protocol the whole ecosystem understands.

The Intuition

In Python, you'd write a `__init__` that accepts multiple types, or a `@classmethod` factory. In Java, you'd write a static `of()` factory method or a constructor. In OCaml, explicit conversion functions with consistent naming conventions. In Rust, `From`/`Into` are the standard interface โ€” implement once, integrate everywhere. The free `Into` impl is the key insight: Rust provides a blanket `impl<T, U: From<T>> Into<U> for T`. So implementing `From<Celsius> for Fahrenheit` automatically makes `Celsius: Into<Fahrenheit>`. You only ever need to implement `From`.

How It Works in Rust

#[derive(Debug, Clone, Copy)]
struct Celsius(f64);

#[derive(Debug, Clone, Copy)]
struct Fahrenheit(f64);

// Implement From โ€” Into comes free
impl From<Celsius> for Fahrenheit {
 fn from(c: Celsius) -> Self {
     Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
 }
}

let c = Celsius(100.0);
let f: Fahrenheit = c.into();          // Into<Fahrenheit> is free from From impl
let f2 = Fahrenheit::from(Celsius(0.0)); // explicit From call also works
// Into in generic functions โ€” accepts any type that can become Celsius
fn print_temperature<T: Into<Celsius>>(temp: T) {
 let c: Celsius = temp.into();   // converts whatever T is into Celsius
 println!("Temperature: {:.1}ยฐC", c.0);
}

print_temperature(Celsius(37.0));       // T = Celsius
print_temperature(Fahrenheit(98.6));    // T = Fahrenheit (via our From impl)
// TryFrom for fallible conversions
impl TryFrom<&str> for Point {
 type Error = String;

 fn try_from(s: &str) -> Result<Self, Self::Error> {
     let parts: Vec<&str> = s.split(',').collect();
     if parts.len() != 2 { return Err("Expected x,y".into()); }
     let x = parts[0].trim().parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
     let y = parts[1].trim().parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
     Ok(Point { x, y })
 }
}

// TryInto comes free from TryFrom, just like Into from From
let p: Result<Point, _> = Point::try_from("3, 4");
// Bidirectional conversions between tuples and structs
impl From<(i32, i32)> for Point { fn from((x, y): (i32, i32)) -> Self { Point { x, y } } }
impl From<Point> for (i32, i32) { fn from(p: Point) -> Self { (p.x, p.y) } }

let p: Point = (3, 4).into();
let t: (i32, i32) = p.into();

What This Unlocks

Key Differences

ConceptOCamlRust
Infallible conversionExplicit function `celsius_to_fahrenheit``impl From<Celsius> for Fahrenheit`
Fallible conversionReturns `option` or `result` type`impl TryFrom<T>` returns `Result`
Free inverseMust implement separately`Into` automatically derived from `From`
Generic acceptanceParametric polymorphism + modules`impl Into<T>` bound in function signature
`?` operator supportNo equivalentUses `From` for error type coercion
// Example 084: From/Into Traits
// OCaml coercion โ†’ Rust explicit conversions

use std::fmt;

// === Approach 1: From trait for type conversions ===
#[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)
    }
}

impl fmt::Display for Celsius {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.1}ยฐC", self.0)
    }
}

impl fmt::Display for Fahrenheit {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.1}ยฐF", self.0)
    }
}

// === Approach 2: From for string parsing (TryFrom for fallible) ===
#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl TryFrom<&str> for Point {
    type Error = String;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        let s = s.trim_start_matches('(').trim_end_matches(')');
        let parts: Vec<&str> = s.split(',').map(str::trim).collect();
        if parts.len() != 2 {
            return Err("Expected (x, y)".to_string());
        }
        let x = parts[0].parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
        let y = parts[1].parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
        Ok(Point { x, y })
    }
}

// From<Point> for (i32, i32)
impl From<Point> for (i32, i32) {
    fn from(p: Point) -> Self {
        (p.x, p.y)
    }
}

impl From<(i32, i32)> for Point {
    fn from((x, y): (i32, i32)) -> Self {
        Point { x, y }
    }
}

// === Approach 3: Into in generic contexts ===
fn print_temperature<T: Into<Celsius>>(temp: T) {
    let c: Celsius = temp.into();
    println!("Temperature: {}", c);
}

// From/Into chain
fn fahrenheit_string_to_celsius(s: &str) -> Result<String, String> {
    let val: f64 = s.parse().map_err(|e: std::num::ParseFloatError| e.to_string())?;
    let c: Celsius = Fahrenheit(val).into(); // Into comes free from From
    Ok(format!("{}", c))
}

// Collecting with From
fn strings_to_points(data: &[(i32, i32)]) -> Vec<Point> {
    data.iter().copied().map(Point::from).collect()
}

fn main() {
    // From/Into temperature
    let c = Celsius(100.0);
    let f: Fahrenheit = c.into();
    println!("{} = {}", c, f);

    let f2 = Fahrenheit(32.0);
    let c2: Celsius = f2.into();
    println!("{} = {}", f2, c2);

    // Generic Into
    print_temperature(Fahrenheit(98.6));
    print_temperature(Celsius(37.0));

    // TryFrom for parsing
    let p: Result<Point, _> = Point::try_from("(3, 4)");
    println!("Parsed: {:?}", p);

    // From tuple
    let p2: Point = (5, 6).into();
    println!("From tuple: {}", p2);

    // Chain
    println!("{}", fahrenheit_string_to_celsius("212").unwrap());
}

#[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() < 1e-10);
    }

    #[test]
    fn test_fahrenheit_to_celsius() {
        let c: Celsius = Fahrenheit(32.0).into();
        assert!((c.0 - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_roundtrip() {
        let original = Celsius(37.0);
        let back: Celsius = Fahrenheit::from(original).into();
        assert!((back.0 - 37.0).abs() < 1e-10);
    }

    #[test]
    fn test_point_try_from() {
        assert_eq!(Point::try_from("(3, 4)"), Ok(Point { x: 3, y: 4 }));
        assert!(Point::try_from("invalid").is_err());
    }

    #[test]
    fn test_point_from_tuple() {
        let p: Point = (1, 2).into();
        assert_eq!(p, Point { x: 1, y: 2 });
    }

    #[test]
    fn test_tuple_from_point() {
        let t: (i32, i32) = Point { x: 5, y: 6 }.into();
        assert_eq!(t, (5, 6));
    }

    #[test]
    fn test_fahrenheit_string_to_celsius() {
        let result = fahrenheit_string_to_celsius("212");
        assert_eq!(result, Ok("100.0ยฐC".to_string()));
        assert!(fahrenheit_string_to_celsius("abc").is_err());
    }

    #[test]
    fn test_strings_to_points() {
        let pts = strings_to_points(&[(1, 2), (3, 4)]);
        assert_eq!(pts.len(), 2);
        assert_eq!(pts[0], Point { x: 1, y: 2 });
    }
}
(* Example 084: From/Into Traits *)
(* OCaml coercion โ†’ Rust explicit conversions *)

(* Approach 1: Explicit conversion functions *)
type celsius = { c : float }
type fahrenheit = { f : float }

let celsius_of_float v = { c = v }
let fahrenheit_of_float v = { f = v }
let fahrenheit_of_celsius c = { f = c.c *. 9.0 /. 5.0 +. 32.0 }
let celsius_of_fahrenheit f = { c = (f.f -. 32.0) *. 5.0 /. 9.0 }

(* Approach 2: String conversions *)
let int_of_string_opt s =
  try Some (int_of_string s) with Failure _ -> None

let string_of_point (x, y) =
  Printf.sprintf "(%d, %d)" x y

let point_of_string s =
  try Scanf.sscanf s "(%d, %d)" (fun x y -> Some (x, y))
  with _ -> None

(* Approach 3: Conversion via module signatures *)
module type Convertible = sig
  type src
  type dst
  val convert : src -> dst
end

module IntToFloat : Convertible with type src = int and type dst = float = struct
  type src = int
  type dst = float
  let convert = float_of_int
end

module StringToChars : Convertible with type src = string and type dst = char list = struct
  type src = string
  type dst = char list
  let convert s = List.init (String.length s) (String.get s)
end

(* Chained conversions *)
let celsius_string_of_fahrenheit_string s =
  match float_of_string_opt s with
  | None -> None
  | Some v ->
    let f = fahrenheit_of_float v in
    let c = celsius_of_fahrenheit f in
    Some (Printf.sprintf "%.1fยฐC" c.c)

(* Tests *)
let () =
  let c = celsius_of_float 100.0 in
  let f = fahrenheit_of_celsius c in
  assert (abs_float (f.f -. 212.0) < 0.01);

  let f2 = fahrenheit_of_float 32.0 in
  let c2 = celsius_of_fahrenheit f2 in
  assert (abs_float (c2.c -. 0.0) < 0.01);

  assert (int_of_string_opt "42" = Some 42);
  assert (int_of_string_opt "abc" = None);

  assert (string_of_point (3, 4) = "(3, 4)");
  assert (point_of_string "(3, 4)" = Some (3, 4));

  assert (IntToFloat.convert 42 = 42.0);
  assert (StringToChars.convert "hi" = ['h'; 'i']);

  assert (celsius_string_of_fahrenheit_string "212" = Some "100.0ยฐC");
  assert (celsius_string_of_fahrenheit_string "abc" = None);

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: From/Into Traits

Infallible Conversion

OCaml:

๐Ÿช Show OCaml equivalent
let fahrenheit_of_celsius c = { f = c.c *. 9.0 /. 5.0 +. 32.0 }
let celsius_of_fahrenheit f = { c = (f.f -. 32.0) *. 5.0 /. 9.0 }

Rust:

impl From<Celsius> for Fahrenheit {
 fn from(c: Celsius) -> Self {
     Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
 }
}
// Into<Celsius> for Fahrenheit comes free!
let c: Celsius = Fahrenheit(212.0).into();

Fallible Conversion

OCaml:

๐Ÿช Show OCaml equivalent
let int_of_string_opt s =
try Some (int_of_string s) with Failure _ -> None

Rust:

impl TryFrom<&str> for Point {
 type Error = String;
 fn try_from(s: &str) -> Result<Self, Self::Error> {
     // parse "(x, y)" format
 }
}
let p = Point::try_from("(3, 4)")?;

Generic Into Bounds

OCaml:

๐Ÿช Show OCaml equivalent
(* Must pass conversion function explicitly *)
let print_celsius convert temp =
let c = convert temp in
Printf.printf "%.1fยฐC" c.c

Rust:

fn print_temperature<T: Into<Celsius>>(temp: T) {
 let c: Celsius = temp.into();
 println!("Temperature: {}", c);
}
print_temperature(Fahrenheit(98.6));  // auto-converts
print_temperature(Celsius(37.0));     // identity