ExamplesBy LevelBy TopicLearning Paths
438 Fundamental

438: `format_args!` and Efficient Formatting

Functional Programming

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

  • • Understand format_args! as the zero-allocation format descriptor used by write! and writeln!
  • • Learn how write!(buf, ...) is more efficient than buf.push_str(&format!(...)) for buffer building
  • • See how format width ({:>width$}) and precision specifiers work programmatically
  • • Understand std::fmt::Write trait enabling custom write targets
  • • Learn the format performance hierarchy: write! > format! > string concatenation
  • Code 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

  • Type-checked formats: Both Rust and OCaml check format strings at compile time; Rust uses a macro-based approach, OCaml uses format type GADT inference.
  • Zero-copy writing: Rust's write! avoids intermediate allocation; OCaml's Buffer.add_string is similarly efficient.
  • Dynamic width: Rust uses {:>width$} with named width; OCaml uses Printf.sprintf "%*s" width s for dynamic width.
  • Custom targets: Rust's 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"));
        }
    }
    ✓ Tests Rust test suite
    #[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

  • Rust macros operate at compile time
  • OCaml uses ppx for similar metaprogramming
  • Both languages support powerful code generation
  • Rust's macro_rules! is built into the language
  • OCaml's approach requires external tooling
  • Exercises

  • Log formatter: Implement 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).
  • Table formatter: Build a TableWriter implementing fmt::Write that collects rows and emits aligned column output when finish() is called, computing column widths from all rows.
  • Number formatting: Extend 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.
  • Open Source Repos