[][src]Macro lazy_format::lazy_format

macro_rules! lazy_format {
    ($pattern:literal) => { ... };
    ($pattern:literal, $($args:tt)*) => { ... };
    (match ($condition:expr) {
        $($match_pattern:pat $(if $guard:expr)? => ($pattern:literal $($args:tt)*)),* $(,)?
    }) => { ... };
    (
        if $condition:expr => ($pattern:literal $($args:tt)*)
        $(else if $elseif_condition:expr => ($elseif_pattern:literal $($elseif_args:tt)*))*
        else $(=>)? ($else_pattern:literal $($else_args:tt)*)
    ) => { ... };
    ($pattern:literal for $item:ident in $collection:expr) => { ... };
    (($pattern:literal $($args:tt)*) for $item:ident in $collection:expr) => { ... };
}

Lazily format something. Essentially the same as format!, except that instead of formatting its arguments to a string, it captures them in an opaque struct, which can be formatted later. This allows you to build up formatting operations without any intermediary allocations or extra formatting calls. Also supports lazy conditional and looping constructs.

The return value of this macro is left deliberately unspecified and undocumented. The most important this about it is its Display implementation, which executes the deferred formatting operation. It also provides a Debug implementation, which simply prints the lazy_format!(...) call without evaluating any of its arguments, as well as Clone and Copy if those traits are available in the captured context.

Note that this macro is completely lazy; it captures the expressions to be formatted in the struct and doesn't evaluate them until the struct is actually written to a String or File or or other writable destination. This means that the argument expression will be evaluated every time the instance is written, which may not be what you want. See semi_lazy_format! for a macro which eagerly evaluates its arguments but lazily does the final formatting.

Basic example:

use std::fmt::Display;
use lazy_format::lazy_format;

fn get_hello() -> String {
    String::from("Hello")
}

fn get_world() -> String {
    String::from("World")
}

fn hello_world() -> impl Display {
    lazy_format!("{}, {w}!", get_hello(), w = get_world())
}

let result = hello_world();

// get_hello and get_world aren't called until the object is
// formatted into a String.
let result_str = result.to_string();
assert_eq!(result_str, "Hello, World!");

Conditional formatting

lazy_format! supports conditional formatting with match- or if- like syntax. When doing a conditional format, add the formatting pattern and arguments directly into the match arms or if blocks, rather than code; this allows conditional formatting to still be captured in a single static type.

match conditional example:

use std::fmt::Display;
use lazy_format::lazy_format;

fn get_number(num: usize) -> impl Display {
    // Note that the parenthesis in the match conditional are required,
    // due to limitations in Rust's macro parsing (can't follow an
    // expression with `{}`)
    lazy_format!(match (num) {
        0 => ("Zero"),
        1 => ("One"),
        2 => ("Two"),
        3 => ("Three"),
        value if value % 2 == 0 => ("A large even number: {}", value),
        value => ("An unrecognized number: {v}", v = value),
    })
}

assert_eq!(get_number(0).to_string(), "Zero");
assert_eq!(get_number(1).to_string(), "One");
assert_eq!(get_number(2).to_string(), "Two");
assert_eq!(get_number(3).to_string(), "Three");
assert_eq!(get_number(4).to_string(), "A large even number: 4");
assert_eq!(get_number(5).to_string(), "An unrecognized number: 5");

if conditional example:

use std::fmt::Display;
use lazy_format::lazy_format;

fn describe_number(value: isize) -> impl Display {
    lazy_format!(
        if value < 0 => ("A negative number: {}", value)
        else if value % 3 == 0 => ("A number divisible by 3: {}", value)
        else if value % 2 == 1 => ("An odd number: {}", value)
        else ("Some other kind of number")
    )
}

assert_eq!(describe_number(-2).to_string(), "A negative number: -2");
assert_eq!(describe_number(-1).to_string(), "A negative number: -1");
assert_eq!(describe_number(0).to_string(), "A number divisible by 3: 0");
assert_eq!(describe_number(1).to_string(), "An odd number: 1");
assert_eq!(describe_number(2).to_string(), "Some other kind of number");
assert_eq!(describe_number(3).to_string(), "A number divisible by 3: 3");

Looping formatting

lazy_format! supports formatting elements in a collection with a loop. There are a few supported syntaxes:

use std::fmt::Display;
use lazy_format::lazy_format;

let list = vec![1i32, 2, 3, 4];
let list_ref = &list;

// Format each element in the iterable without additional arguments to `format_args`
let simple_semicolons = lazy_format!("{v}; " for v in list_ref.iter().map(|x| x - 1));
assert_eq!(simple_semicolons.to_string(), "0; 1; 2; 3; ");

// Perform a full format with additional arguments on each element in the iterable.
let header = "Value";
let full_format = lazy_format!(("{}: {}; ", header, v) for v in list_ref);
assert_eq!(full_format.to_string(), "Value: 1; Value: 2; Value: 3; Value: 4; ");

Note that these looping formatters are not suitable for doing something like a comma separated list, since they'll apply the formatting to all elements. For a lazy string joining library, which only inserts separators between elements in a list, check out joinery.