438: `format_args!` and Efficient Formatting
Tutorial Video
Text description (accessibility)
This video demonstrates the "438: `format_args!` and Efficient Formatting" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. String formatting is ubiquitous but has performance implications. Key difference from OCaml: 1. **Type
Tutorial
The Problem
String formatting is ubiquitous but has performance implications. format!("{}: {}", name, value) always allocates a new String. For logging, writing to a buffer, or outputting to a Write implementor, you want to format directly without intermediate allocation. format_args! returns a fmt::Arguments value — a lightweight descriptor of the format string and its arguments — that can be passed to write!, writeln!, or any fmt::Write implementor. This avoids the intermediate String allocation when the target already implements Write.
format_args! is the foundation of all Rust's format macros: format!, println!, writeln!, eprintln!, log::info! — they all ultimately receive fmt::Arguments.
🎯 Learning Outcomes
format_args! as the zero-allocation format descriptor used by write! and writeln!write!(buf, ...) is more efficient than buf.push_str(&format!(...)) for buffer building{:>width$}) and precision specifiers work programmaticallystd::fmt::Write trait enabling custom write targetswrite! > format! > string concatenationCode Example
#![allow(clippy::all)]
//! format_args! and Formatting
//!
//! Efficient string formatting.
use std::fmt::Write;
/// Write formatted data to a buffer.
pub fn write_to_buffer(buf: &mut String, name: &str, value: i32) {
write!(buf, "{}: {}", name, value).unwrap();
}
/// Format with padding.
pub fn format_padded(s: &str, width: usize) -> String {
format!("{:>width$}", s, width = width)
}
/// Format number with thousands separator.
pub fn format_number(n: u64) -> String {
let s = n.to_string();
let mut result = String::new();
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.chars().rev().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write_to_buffer() {
let mut buf = String::new();
write_to_buffer(&mut buf, "count", 42);
assert_eq!(buf, "count: 42");
}
#[test]
fn test_format_padded() {
assert_eq!(format_padded("hi", 5), " hi");
}
#[test]
fn test_format_number() {
assert_eq!(format_number(1000), "1,000");
assert_eq!(format_number(1000000), "1,000,000");
}
#[test]
fn test_format_small_number() {
assert_eq!(format_number(42), "42");
}
#[test]
fn test_multiple_writes() {
let mut buf = String::new();
write_to_buffer(&mut buf, "a", 1);
buf.push_str(", ");
write_to_buffer(&mut buf, "b", 2);
assert!(buf.contains("a: 1"));
assert!(buf.contains("b: 2"));
}
}Key Differences
format type GADT inference.write! avoids intermediate allocation; OCaml's Buffer.add_string is similarly efficient.{:>width$} with named width; OCaml uses Printf.sprintf "%*s" width s for dynamic width.fmt::Write trait enables any type to be a format target; OCaml's Format.formatter is the universal output target.OCaml Approach
OCaml's Printf.sprintf, Format.sprintf, and Buffer.add_string correspond to Rust's formatting approaches. Buffer.t is the direct equivalent of String as a write target. Format.fprintf fmt "..." args writes to a formatter without intermediate allocation. OCaml's format type (for Printf.printf/Format.printf) is type-checked at compile time similarly to Rust's format strings.
Full Source
#![allow(clippy::all)]
//! format_args! and Formatting
//!
//! Efficient string formatting.
use std::fmt::Write;
/// Write formatted data to a buffer.
pub fn write_to_buffer(buf: &mut String, name: &str, value: i32) {
write!(buf, "{}: {}", name, value).unwrap();
}
/// Format with padding.
pub fn format_padded(s: &str, width: usize) -> String {
format!("{:>width$}", s, width = width)
}
/// Format number with thousands separator.
pub fn format_number(n: u64) -> String {
let s = n.to_string();
let mut result = String::new();
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.chars().rev().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write_to_buffer() {
let mut buf = String::new();
write_to_buffer(&mut buf, "count", 42);
assert_eq!(buf, "count: 42");
}
#[test]
fn test_format_padded() {
assert_eq!(format_padded("hi", 5), " hi");
}
#[test]
fn test_format_number() {
assert_eq!(format_number(1000), "1,000");
assert_eq!(format_number(1000000), "1,000,000");
}
#[test]
fn test_format_small_number() {
assert_eq!(format_number(42), "42");
}
#[test]
fn test_multiple_writes() {
let mut buf = String::new();
write_to_buffer(&mut buf, "a", 1);
buf.push_str(", ");
write_to_buffer(&mut buf, "b", 2);
assert!(buf.contains("a: 1"));
assert!(buf.contains("b: 2"));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write_to_buffer() {
let mut buf = String::new();
write_to_buffer(&mut buf, "count", 42);
assert_eq!(buf, "count: 42");
}
#[test]
fn test_format_padded() {
assert_eq!(format_padded("hi", 5), " hi");
}
#[test]
fn test_format_number() {
assert_eq!(format_number(1000), "1,000");
assert_eq!(format_number(1000000), "1,000,000");
}
#[test]
fn test_format_small_number() {
assert_eq!(format_number(42), "42");
}
#[test]
fn test_multiple_writes() {
let mut buf = String::new();
write_to_buffer(&mut buf, "a", 1);
buf.push_str(", ");
write_to_buffer(&mut buf, "b", 2);
assert!(buf.contains("a: 1"));
assert!(buf.contains("b: 2"));
}
}
Deep Comparison
OCaml vs Rust: macro format args
See example.rs and example.ml for side-by-side implementations.
Key Points
Exercises
struct LogLine with timestamp: u64, level: &str, message: &str. Use write!(buf, ...) to format it as "[{timestamp}] {LEVEL}: {message}" into a pre-allocated String buffer, benchmarking against format!("{}", line).TableWriter implementing fmt::Write that collects rows and emits aligned column output when finish() is called, computing column widths from all rows.format_number to support different separators (, for US, . for EU, _ for code), and add format_currency(n: f64, symbol: &str, decimal_places: u8) for monetary formatting.