Need a quick Display impl without defining a whole new type? std::fmt::from_fn turns any closure into a formattable value — ad-hoc Display in one line.
The problem: Display needs a type
Normally, implementing Display means creating a wrapper struct just to control how something gets formatted:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| use std::fmt;
struct CommaSeparated<'a>(&'a [i32]);
impl fmt::Display for CommaSeparated<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, val) in self.0.iter().enumerate() {
if i > 0 { write!(f, ", ")?; }
write!(f, "{val}")?;
}
Ok(())
}
}
fn main() {
let nums = vec![1, 2, 3];
let display = CommaSeparated(&nums);
assert_eq!(display.to_string(), "1, 2, 3");
}
|
That’s a lot of boilerplate for “join with commas.”
After: fmt::from_fn
Stabilized in Rust 1.93, std::fmt::from_fn takes a closure Fn(&mut Formatter) -> fmt::Result and returns a value that implements Display (and Debug):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| use std::fmt;
fn main() {
let nums = vec![1, 2, 3];
let display = fmt::from_fn(|f| {
for (i, val) in nums.iter().enumerate() {
if i > 0 { write!(f, ", ")?; }
write!(f, "{val}")?;
}
Ok(())
});
assert_eq!(display.to_string(), "1, 2, 3");
// Works directly in format strings too
assert_eq!(format!("numbers: {display}"), "numbers: 1, 2, 3");
}
|
No wrapper type, no trait impl — just a closure that writes to a formatter.
Wrap from_fn in a function and you have a reusable formatter without the boilerplate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| use std::fmt;
fn join_with<'a, I, T>(iter: I, sep: &'a str) -> impl fmt::Display + 'a
where
I: IntoIterator<Item = T> + 'a,
T: fmt::Display + 'a,
{
fmt::from_fn(move |f| {
let mut first = true;
for item in iter {
if !first { write!(f, "{sep}")?; }
first = false;
write!(f, "{item}")?;
}
Ok(())
})
}
fn main() {
let tags = vec!["rust", "formatting", "closures"];
assert_eq!(join_with(tags, " | ").to_string(), "rust | formatting | closures");
let nums = vec![10, 20, 30];
assert_eq!(format!("sum of [{}]", join_with(nums, " + ")), "sum of [10 + 20 + 30]");
}
|
from_fn is lazy — the closure only runs when the value is actually formatted. This makes it perfect for logging where most messages might be filtered out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| use std::fmt;
fn debug_summary(data: &[u8]) -> impl fmt::Display + '_ {
fmt::from_fn(move |f| {
write!(f, "[{} bytes: ", data.len())?;
for (i, byte) in data.iter().take(4).enumerate() {
if i > 0 { write!(f, " ")?; }
write!(f, "{byte:02x}")?;
}
if data.len() > 4 { write!(f, " ...")?; }
write!(f, "]")
})
}
fn main() {
let payload = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x42, 0x00];
let summary = debug_summary(&payload);
// The closure hasn't run yet — zero work done
// It only formats when you actually use it:
assert_eq!(summary.to_string(), "[6 bytes: de ad be ef ...]");
}
|
No String is allocated unless something actually calls Display::fmt. Compare that to eagerly building a debug string just in case.
from_fn also implements Debug
The returned value implements both Display and Debug, so it works with {:?} too:
1
2
3
4
5
6
7
| use std::fmt;
fn main() {
let val = fmt::from_fn(|f| write!(f, "custom"));
assert_eq!(format!("{val}"), "custom");
assert_eq!(format!("{val:?}"), "custom");
}
|
fmt::from_fn is a tiny addition to std that removes a surprisingly common friction point — any time you’d reach for a newtype just to implement Display, try a closure instead.