ExamplesBy LevelBy TopicLearning Paths
474 Fundamental

String Formatting

Functional Programming

Tutorial

The Problem

Dynamic string construction is one of the most frequent programming operations: generating output, building error messages, constructing SQL queries, serialising data. C's printf has no compile-time safety. Python's f-strings and Java's String.format check formats at runtime. Rust's format macros check format specifiers against argument types at compile time, making mismatches a build error rather than a runtime panic or silent truncation.

🎯 Learning Outcomes

  • • Use alignment specifiers {:>width}, {:<width}, {:^width} for right/left/centre padding
  • • Format numbers: hexadecimal {:x}, binary {:b}, octal {:o}, floating-point precision {:.2}
  • • Write directly into a String with write!(buf, ...) using fmt::Write
  • • Format collections with {:?} (Debug) vs. {} (Display)
  • • Understand format_args! as the zero-allocation building block underlying all format macros
  • Code Example

    #![allow(clippy::all)]
    // 474. format!, write!, writeln!
    use std::fmt::Write as FmtWrite;
    
    #[cfg(test)]
    mod tests {
        use std::fmt::Write;
        #[test]
        fn test_align() {
            assert_eq!(format!("{:>5}", "hi"), "   hi");
            assert_eq!(format!("{:<5}", "hi"), "hi   ");
        }
        #[test]
        fn test_nums() {
            assert_eq!(format!("{:x}", 255u8), "ff");
            assert_eq!(format!("{:.2}", 3.14159f64), "3.14");
        }
        #[test]
        fn test_write() {
            let mut s = String::new();
            write!(s, "{}", 42).unwrap();
            assert_eq!(s, "42");
        }
        #[test]
        fn test_debug() {
            assert_eq!(format!("{:?}", vec![1, 2, 3]), "[1, 2, 3]");
        }
    }

    Key Differences

  • Compile-time checking: Both Rust and OCaml check format strings at compile time; Rust uses macro expansion, OCaml uses GADT-typed format strings.
  • Trait-based extensibility: Rust's format system is extensible — any type implementing Display or Debug works; OCaml requires explicit format converters.
  • **write! to buffer**: Rust's fmt::Write trait allows writing into any buffer (including Vec<u8> with io::Write); OCaml uses Buffer.add_string + Printf.bprintf.
  • Named arguments: Rust supports {name} named format arguments; OCaml's Printf uses positional only.
  • OCaml Approach

    OCaml uses Printf.printf/Printf.sprintf for C-style formatting and Format.printf for structured pretty-printing:

    Printf.sprintf "%5s" "hi"         (* "   hi" *)
    Printf.sprintf "%.2f" 3.14159     (* "3.14"  *)
    Printf.sprintf "%x" 255           (* "ff"    *)
    

    OCaml 4.08+ added Format.sprintf with combinators for complex structures. The Fmt library provides composable formatters similar to Rust's Display/Debug split. OCaml's Printf formats are checked at compile time via GADT magic on format strings.

    Full Source

    #![allow(clippy::all)]
    // 474. format!, write!, writeln!
    use std::fmt::Write as FmtWrite;
    
    #[cfg(test)]
    mod tests {
        use std::fmt::Write;
        #[test]
        fn test_align() {
            assert_eq!(format!("{:>5}", "hi"), "   hi");
            assert_eq!(format!("{:<5}", "hi"), "hi   ");
        }
        #[test]
        fn test_nums() {
            assert_eq!(format!("{:x}", 255u8), "ff");
            assert_eq!(format!("{:.2}", 3.14159f64), "3.14");
        }
        #[test]
        fn test_write() {
            let mut s = String::new();
            write!(s, "{}", 42).unwrap();
            assert_eq!(s, "42");
        }
        #[test]
        fn test_debug() {
            assert_eq!(format!("{:?}", vec![1, 2, 3]), "[1, 2, 3]");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use std::fmt::Write;
        #[test]
        fn test_align() {
            assert_eq!(format!("{:>5}", "hi"), "   hi");
            assert_eq!(format!("{:<5}", "hi"), "hi   ");
        }
        #[test]
        fn test_nums() {
            assert_eq!(format!("{:x}", 255u8), "ff");
            assert_eq!(format!("{:.2}", 3.14159f64), "3.14");
        }
        #[test]
        fn test_write() {
            let mut s = String::new();
            write!(s, "{}", 42).unwrap();
            assert_eq!(s, "42");
        }
        #[test]
        fn test_debug() {
            assert_eq!(format!("{:?}", vec![1, 2, 3]), "[1, 2, 3]");
        }
    }

    Exercises

  • Table formatter: Write format_table(headers: &[&str], rows: &[Vec<String>]) -> String that right-pads each column to its maximum width.
  • Custom Display: Implement fmt::Display for a Matrix(Vec<Vec<f64>>) type that formats it as space-separated rows, each on its own line with 2-decimal-place precision.
  • No-alloc formatting: Use format_args! with a custom fmt::Write implementor that writes directly to a fixed-size [u8; 128] buffer without heap allocation.
  • Open Source Repos