Skip to content

Commit

Permalink
Unrolled build for #136662
Browse files Browse the repository at this point in the history
Rollup merge of #136662 - thaliaarchi:formatter-pad-char-count, r=m-ou-se

Count char width at most once in `Formatter::pad`

When both width and precision flags are specified, then `Formatter::pad` counts the character width twice. Instead, record the character width when truncating it to the precision, so it does not need to be recomputed. Simplify control flow so the cases are more clear.

Related:
- 6c9e708 (`fmt::Formatter::pad`: don't call chars().count() more than one time, 2021-09-01): Reduce counting chars from thrice to twice in worst case
- ede39ae (feat: reinterpret `precision` field for strings, 2016-06-29): Change meaning of precision for strings
- b820748 (Implement formatting arguments for strings and integers, 2013-08-10): Implement `Formatter::pad`
  • Loading branch information
rust-timer authored Mar 5, 2025
2 parents 4559163 + 0ca1c9c commit cb2f04e
Showing 1 changed file with 29 additions and 37 deletions.
66 changes: 29 additions & 37 deletions library/core/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1693,49 +1693,41 @@ impl<'a> Formatter<'a> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn pad(&mut self, s: &str) -> Result {
// Make sure there's a fast path up front
// Make sure there's a fast path up front.
if self.options.width.is_none() && self.options.precision.is_none() {
return self.buf.write_str(s);
}
// The `precision` field can be interpreted as a `max-width` for the

// The `precision` field can be interpreted as a maximum width for the
// string being formatted.
let s = if let Some(max) = self.options.precision {
// If our string is longer that the precision, then we must have
// truncation. However other flags like `fill`, `width` and `align`
// must act as always.
if let Some((i, _)) = s.char_indices().nth(max) {
// LLVM here can't prove that `..i` won't panic `&s[..i]`, but
// we know that it can't panic. Use `get` + `unwrap_or` to avoid
// `unsafe` and otherwise don't emit any panic-related code
// here.
s.get(..i).unwrap_or(s)
} else {
&s
}
let (s, char_count) = if let Some(max_char_count) = self.options.precision {
let mut iter = s.char_indices();
let remaining = match iter.advance_by(max_char_count) {
Ok(()) => 0,
Err(remaining) => remaining.get(),
};
// SAFETY: The offset of `.char_indices()` is guaranteed to be
// in-bounds and between character boundaries.
let truncated = unsafe { s.get_unchecked(..iter.offset()) };
(truncated, max_char_count - remaining)
} else {
&s
// Use the optimized char counting algorithm for the full string.
(s, s.chars().count())
};
// The `width` field is more of a `min-width` parameter at this point.
match self.options.width {
// If we're under the maximum length, and there's no minimum length
// requirements, then we can just emit the string
None => self.buf.write_str(s),
Some(width) => {
let chars_count = s.chars().count();
// If we're under the maximum width, check if we're over the minimum
// width, if so it's as easy as just emitting the string.
if chars_count >= width {
self.buf.write_str(s)
}
// If we're under both the maximum and the minimum width, then fill
// up the minimum width with the specified string + some alignment.
else {
let align = Alignment::Left;
let post_padding = self.padding(width - chars_count, align)?;
self.buf.write_str(s)?;
post_padding.write(self)
}
}

// The `width` field is more of a minimum width parameter at this point.
if let Some(width) = self.options.width
&& char_count < width
{
// If we're under the minimum width, then fill up the minimum width
// with the specified string + some alignment.
let post_padding = self.padding(width - char_count, Alignment::Left)?;
self.buf.write_str(s)?;
post_padding.write(self)
} else {
// If we're over the minimum width or there is no minimum width, we
// can just emit the string.
self.buf.write_str(s)
}
}

Expand Down

0 comments on commit cb2f04e

Please sign in to comment.