Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Format specifier for truncating string-like types

Tags:

rust

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.

like image 752
Michael Dorst Avatar asked Oct 18 '25 03:10

Michael Dorst


2 Answers

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!

like image 141
Silvio Mayolo Avatar answered Oct 20 '25 19:10

Silvio Mayolo


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).

like image 27
Michael Dorst Avatar answered Oct 20 '25 19:10

Michael Dorst