Macro lazy_format::prelude::lazy_format
source · macro_rules! lazy_format { ($pattern:literal $(, $($args:tt)*)?) => { ... }; (match ($condition:expr) { $($( $match_pattern:pat $(if $guard:expr)? => $output:tt ),+ $(,)?)? }) => { ... }; ( if $(let $match:pat = )? $condition:expr => $output:tt $(else if $(let $elseif_match:pat = )? $elseif_condition:expr => $elseif_output:tt)* $(else => $else_output:tt)? ) => { ... }; ($output:tt for $item:pat in $collection:expr) => { ... }; }
Expand description
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; be sure to eagerly perform
any 1-time calculations you want to before calling lazy_format!
.
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!");
Just like with regular formatting, lazy_format
can automatically, implicitly
capture named parameters:
use std::mem::{size_of_val, size_of};
use lazy_format::lazy_format;
let a = 10;
let b = 20;
let result = lazy_format!("{a} {b}");
assert_eq!(size_of_val(&result), size_of::<i32>() * 2);
assert_eq!(result.to_string(), "10 20");
Demonstation of lazy capturing:
use std::fmt::Display;
use std::mem::{size_of_val, size_of};
use lazy_format::lazy_format;
fn get_formatted() -> impl Display {
let a: isize = 10;
let b: isize = 15;
lazy_format!("10 + 15: {v}, again: {v}", v = (a + b))
}
let result = get_formatted();
// The result captures 2 isize values (a and b) from get_formatted.
assert_eq!(size_of_val(&result), size_of::<isize>() * 2);
let result_str = result.to_string();
assert_eq!(result_str, "10 + 15: 25, again: 25");
Conditional formatting
lazy_format!
supports conditional formatting with match
- or if
-
style 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",
4 | 5 => "Four or five",
value if value % 2 == 0 => ("A large even number: {}", value),
value => "An unrecognized number: {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(), "Four or five");
assert_eq!(get_number(5).to_string(), "Four or five");
assert_eq!(get_number(6).to_string(), "A large even number: 6");
assert_eq!(get_number(7).to_string(), "An unrecognized number: 7");
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");
if
formatters are allowed to exclude the final else
branch, in which
case lazy_format
will simply write an empty string:
use std::fmt::Display;
use lazy_format::lazy_format;
fn only_evens(value: i32) -> impl Display {
lazy_format!(if value % 2 == 0 => ("An even number: {}", value))
}
assert_eq!(only_evens(10).to_string(), "An even number: 10");
assert_eq!(only_evens(5).to_string(), "");
if let
conditional example:
use std::fmt::Display;
use lazy_format::lazy_format;
fn describe_optional_number(value: Option<isize>) -> impl Display {
lazy_format!(
if let Some(10) = value => "It's ten!"
else if let Some(3) | Some(4) = value => "It's three or four!"
else if let | Some(0) = value => "It's zero!"
else if let Some(x) = value => ("It's some other value: {}", x)
else => "It's not a number!"
)
}
assert_eq!(describe_optional_number(Some(10)).to_string(), "It's ten!");
assert_eq!(describe_optional_number(Some(3)).to_string(), "It's three or four!");
assert_eq!(describe_optional_number(Some(4)).to_string(), "It's three or four!");
assert_eq!(describe_optional_number(Some(0)).to_string(), "It's zero!");
assert_eq!(describe_optional_number(Some(5)).to_string(), "It's some other value: 5");
assert_eq!(describe_optional_number(None).to_string(), "It's not a number!");
Looping formatting
lazy_format!
supports formatting elements in a collection with a loop:
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.