Is there a format specifier that will truncate a string-like type? I have a type which is not a String, but does implement Display. I want to limit it to be 7 characters long though, and the only way I know how to do that is to convert it to string first.
let sha1 = format!("{}", branch.commit_id);
let formatted = format!("{} ({})\n", branch.name, &sha1[0..7]));
Is there some format specifier that will let me do that in one step? Something like:
let formatted = format!("{} ({<something 7>})\n", branch.name, branch.commit_id);
Unfortunately, because it is not a string, I can't just do &branch.commit_id[0..7]
, which is why I'm hoping there is a format specifier syntax for that.
As mentioned in the comments, anything which has a Display
instance automatically gets a ToString
instance, so you can call to_string
to get a string and then truncate on any value which implements Display
. That's probably the practical, correct answer that you're looking for.
But for completeness, there's still an interesting question here: can we do the truncation without ever generating the intermediate string? As it happens, we can implement a custom formatter to do just that. Most Display
implementations are more or less calls to the write!
macro, which is just a fancy way of calling write_fmt
. Confusingly, this can be write_fmt
from either the std::io::Write
or the std::fmt::Write
trait, but we'll focus on the latter for the moment. We can implement our own instance of std::fmt::Write
that truncates to a specified length.
pub struct TruncatedFormatter<'a, T> {
pub remaining: usize,
pub inner: &'a mut T,
}
impl<'a, T> fmt::Write for TruncatedFormatter<'a, T> where T : fmt::Write {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.remaining < s.len() {
self.inner.write_str(&s[0..self.remaining])?;
self.remaining = 0;
Ok(())
} else {
self.remaining -= s.len();
self.inner.write_str(s)
}
}
}
Then we can make a thin wrapper around any type T
which forces the type to use our customized formatter. Since fmt
on Display
only requires an immutable reference, our wrapper will only take an immutable reference, to maximize compatibility with the call site.
pub struct TruncatedValue<'a, T>(pub usize, pub &'a T);
impl<'a, T> fmt::Display for TruncatedValue<'a, T> where T : fmt::Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let TruncatedValue(remaining, value) = self;
let mut wrapped_fmt = TruncatedFormatter { remaining: *remaining, inner: f };
write!(wrapped_fmt, "{}", value)
}
}
Then, when we want to print our value truncated to a specific length, we can wrap it during the format!
or println!
call.
println!("Full value '{}'\nTruncated value '{}'\n", test, TruncatedValue(7, &test));
Try it in the Rust Playground!
I stumbled across it a few days later. There is a format specifier which does this.
println!("Hello {:.5}", "world <-- only the the first 5 characters will be printed");
Edit: As Stargateur pointed out in the comments, this does not work in the general case. It happened to work in mine because I was using a git2::Oid
, whose Display
implementation happens to convert itself to str
before calling .fmt(f)
on itself (src).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With