ExamplesBy LevelBy TopicLearning Paths
140 Expert

Type-Safe Printf

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Type-Safe Printf" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. C's `printf` is famously unsafe: `printf("%d", "hello")` compiles but causes undefined behavior at runtime. Key difference from OCaml: 1. **Macro vs. types**: Rust uses macros to parse format strings at compile time; OCaml encodes format strings as values of a GADT type that carries type information.

Tutorial

The Problem

C's printf is famously unsafe: printf("%d", "hello") compiles but causes undefined behavior at runtime. A type-safe printf encodes the format string's type signature in the type system, so passing the wrong type of argument is a compile error. This is one of the motivating examples for GADTs in OCaml and for HLists in Haskell, demonstrating how the type system can enforce format-argument correspondence at zero runtime cost.

🎯 Learning Outcomes

  • • Understand how format strings can be encoded as type-level data structures
  • • Learn to use phantom types or HLists to thread argument types through a format specification
  • • See how Rust's format_args! macro achieves a limited form of type-safe formatting at compile time
  • • Appreciate the gap between Rust's macro-based approach and GADTs-based type-safe printf
  • Code Example

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Key Differences

  • Macro vs. types: Rust uses macros to parse format strings at compile time; OCaml encodes format strings as values of a GADT type that carries type information.
  • Runtime format strings: OCaml's type-safe printf works with statically known format strings only (like Rust's); dynamic format strings in both languages lose type safety.
  • Ergonomics: OCaml's approach integrates naturally into the language; Rust's format! macro achieves safety through a different (macro-based) mechanism.
  • Extensibility: OCaml's GADT format encoding is user-extensible; Rust's format! macro is closed (only built-in specifiers).
  • OCaml Approach

    OCaml's Printf.printf is genuinely type-safe via a clever encoding in the standard library:

    Printf.printf "%d is %s\n" 42 "hello"  (* type: int -> string -> unit *)
    Printf.printf "%d is %s\n" "oops"      (* type error at compile time *)
    

    OCaml encodes format strings as GADTs where the phantom type encodes the argument sequence. The format6 type in OCaml's standard library is the result of decades of refinement to make this ergonomic.

    Full Source

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Deep Comparison

    OCaml vs Rust: Type Safe Printf

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Implement a simplified type-safe format using HLists: Fmt<Int, Fmt<Str, Done>> and a format function that takes the matching HCons<i32, HCons<String, HNil>>.
  • Use Rust's format_args! to verify that passing the wrong type for a {} placeholder is caught at compile time (not runtime).
  • Design a DSL for SQL query parameters using a similar type-encoding to ensure WHERE id = ? receives an integer, not a string.
  • Open Source Repos