Skip to content

Commit 1d26e00

Browse files
committed
Count char width at most once in Formatter::pad
When both width and precision flags are specified, then the character width is counted 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.
1 parent 942db67 commit 1d26e00

File tree

1 file changed

+26
-38
lines changed

1 file changed

+26
-38
lines changed

library/core/src/fmt/mod.rs

+26-38
Original file line numberDiff line numberDiff line change
@@ -1693,49 +1693,37 @@ impl<'a> Formatter<'a> {
16931693
/// ```
16941694
#[stable(feature = "rust1", since = "1.0.0")]
16951695
pub fn pad(&mut self, s: &str) -> Result {
1696-
// Make sure there's a fast path up front
1696+
// Make sure there's a fast path up front.
16971697
if self.options.width.is_none() && self.options.precision.is_none() {
16981698
return self.buf.write_str(s);
16991699
}
1700-
// The `precision` field can be interpreted as a `max-width` for the
1700+
1701+
// The `precision` field can be interpreted as a maximum width for the
17011702
// string being formatted.
1702-
let s = if let Some(max) = self.options.precision {
1703-
// If our string is longer that the precision, then we must have
1704-
// truncation. However other flags like `fill`, `width` and `align`
1705-
// must act as always.
1706-
if let Some((i, _)) = s.char_indices().nth(max) {
1707-
// LLVM here can't prove that `..i` won't panic `&s[..i]`, but
1708-
// we know that it can't panic. Use `get` + `unwrap_or` to avoid
1709-
// `unsafe` and otherwise don't emit any panic-related code
1710-
// here.
1711-
s.get(..i).unwrap_or(s)
1712-
} else {
1713-
&s
1714-
}
1703+
let max_char_count = self.options.precision.unwrap_or(usize::MAX);
1704+
let mut iter = s.chars();
1705+
let char_count = iter.by_ref().take(max_char_count).count();
1706+
1707+
// If our string is longer than the maximum width, truncate it and
1708+
// handle other flags in terms of the truncated string.
1709+
let byte_len = s.len() - iter.as_str().len();
1710+
// SAFETY: The index is derived from the offset of `.chars()`, which is
1711+
// guaranteed to be in-bounds and between character boundaries.
1712+
let s = unsafe { s.get_unchecked(..byte_len) };
1713+
1714+
// The `width` field is more of a minimum width parameter at this point.
1715+
if let Some(width) = self.options.width
1716+
&& char_count < width
1717+
{
1718+
// If we're under the minimum width, then fill up the minimum width
1719+
// with the specified string + some alignment.
1720+
let post_padding = self.padding(width - char_count, Alignment::Left)?;
1721+
self.buf.write_str(s)?;
1722+
post_padding.write(self)
17151723
} else {
1716-
&s
1717-
};
1718-
// The `width` field is more of a `min-width` parameter at this point.
1719-
match self.options.width {
1720-
// If we're under the maximum length, and there's no minimum length
1721-
// requirements, then we can just emit the string
1722-
None => self.buf.write_str(s),
1723-
Some(width) => {
1724-
let chars_count = s.chars().count();
1725-
// If we're under the maximum width, check if we're over the minimum
1726-
// width, if so it's as easy as just emitting the string.
1727-
if chars_count >= width {
1728-
self.buf.write_str(s)
1729-
}
1730-
// If we're under both the maximum and the minimum width, then fill
1731-
// up the minimum width with the specified string + some alignment.
1732-
else {
1733-
let align = Alignment::Left;
1734-
let post_padding = self.padding(width - chars_count, align)?;
1735-
self.buf.write_str(s)?;
1736-
post_padding.write(self)
1737-
}
1738-
}
1724+
// If we're over the minimum width or there is no minimum width, we
1725+
// can just emit the string.
1726+
self.buf.write_str(s)
17391727
}
17401728
}
17411729

0 commit comments

Comments
 (0)