Consider a function that generates a string which is intended for display:
fn post_url(id: u64) -> String {
format!("https://users.rust-lang.org/t/{id}")
}
This function allocates a new String
. What if we want to avoid that? We can change the return type to something that implements Display
. Perhaps format_args!
can be of use?
fn post_url(id: u64) -> impl std::fmt::Display {
format_args!("https://users.rust-lang.org/t/{id}")
}
This fails to compile with the following error:
error[E0716]: temporary value dropped while borrowed
Due to how format_args!
works, we can not assign the result to a variable or return it from a function.
Alright, well in that case we can just implement Display
for a new type that we create:
fn post_url(id: u64) -> impl std::fmt::Display {
struct PostUrl(u64);
impl std::fmt::Display for PostUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "https://users.rust-lang.org/t/{}", self.0)
}
}
PostUrl(id)
}
This implementation does not force a String
allocation.
In general, we may want to concatenate some expressions and literals. I've written a declarative macro that achieves this (Playground):
mod concat_display {
use core::fmt;
macro_rules! zero_sized_literal {
($value:literal) => {{
struct Literal;
impl std::fmt::Display for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$value.fmt(f)
}
}
Literal
}};
}
pub(crate) use zero_sized_literal;
pub struct ConcatDisplay<A: fmt::Display, B: fmt::Display>(pub A, pub B);
impl<A: fmt::Display, B: fmt::Display> fmt::Display for ConcatDisplay<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.0, self.1)
}
}
macro_rules! concat_display {
($a:literal, $b:literal $(,)?) => {
$crate::concat_display::ConcatDisplay($crate::concat_display::zero_sized_literal!($a), $crate::concat_display::zero_sized_literal!($b))
};
($a:literal, $b:expr $(,)?) => {
$crate::concat_display::ConcatDisplay($crate::concat_display::zero_sized_literal!($a), $b)
};
($a:expr, $b:literal $(,)?) => {
$crate::concat_display::ConcatDisplay($a, $crate::concat_display::zero_sized_literal!($b))
};
($a:expr, $b:expr $(,)?) => {
$crate::concat_display::ConcatDisplay($a, $b)
};
($a:literal, $b:literal, $($tail:tt)+) => {
$crate::concat_display::ConcatDisplay($crate::concat_display::zero_sized_literal!($a), $crate::concat_display::concat_display!($b, $($tail)+))
};
($a:literal, $b:expr, $($tail:tt)+) => {
$crate::concat_display::ConcatDisplay($crate::concat_display::zero_sized_literal!($a), $crate::concat_display::concat_display!($b, $($tail)+))
};
($a:expr, $b:literal, $($tail:tt)+) => {
$crate::concat_display::ConcatDisplay($a, $crate::concat_display::concat_display!($b, $($tail)+))
};
($a:expr, $b:expr, $($tail:tt)+) => {
$crate::concat_display::ConcatDisplay($a, $crate::concat_display::concat_display!($b, $($tail)+))
};
}
pub(crate) use concat_display;
}
fn format_something(program: &str, count: usize) -> impl '_ + std::fmt::Display {
concat_display::concat_display!(
"the program is: ",
program,
", and there is/are ",
count,
" arguments in total!"
)
}
fn main() {
let program = std::env::args().skip(0).next().unwrap();
let count = std::env::args().count();
let something_to_display = format_something(program.as_str(), count);
println!("size_of<&str>: {}", std::mem::size_of::<&str>());
println!("size_of<usize>: {}", std::mem::size_of::<usize>());
println!("size_of_val: {}", std::mem::size_of_val(&something_to_display));
println!("type_name_of_val: {}", std::any::type_name_of_val(&something_to_display));
println!("{}", something_to_display);
}
Which prints
size_of<&str>: 16
size_of<usize>: 8
size_of_val: 24
type_name_of_val: playground::concat_display::ConcatDisplay<playground::main::Literal, playground::concat_display::ConcatDisplay<&str, playground::concat_display::ConcatDisplay<playground::main::Literal, playground::concat_display::ConcatDisplay<usize, playground::main::Literal>>>>
the program is: target/debug/playground, and there is/are 1 arguments in total!
I'm wondering:
- Is it worth doing this at all? (in my head this translates to "is the eventually emitted code actually more performant than
format!
", but there may be more aspects to look at, such as code size) - Is there a crate that provides this functionality? (I would imagine so, but couldn't figure out the right search keywords)