String Formatting
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
{:>width}, {:<width}, {:^width} for right/left/centre padding{:x}, binary {:b}, octal {:o}, floating-point precision {:.2}String with write!(buf, ...) using fmt::Write{:?} (Debug) vs. {} (Display)format_args! as the zero-allocation building block underlying all format macrosCode 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
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.{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]");
}
}#[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
format_table(headers: &[&str], rows: &[Vec<String>]) -> String that right-pads each column to its maximum width.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.format_args! with a custom fmt::Write implementor that writes directly to a fixed-size [u8; 128] buffer without heap allocation.