Skip to content

std::fmt: Precision via asterisk does not fit documentation when using named or explicit positional arguments #96413

Closed
@EliasHolzmann

Description

@EliasHolzmann

From the documentation on std::fmt, section "Precision" (highlighting in bold by me):

There are three possible ways to specify the desired precision:

[...]

  1. An asterisk .*:

    .* means that this {...} is associated with two* format inputs rather than one: the first input holds the usize precision, and the second holds the value to print. Note that in this case, if one uses the format string {<arg>:<spec>.*}, then the <arg> part refers to the value to print, and the precision must come in the input preceding <arg>.

The bold part does not fit the current behavior. Consider the following snippet:

fn main() {
    println!("{2:.*} {}", "foobar", 1, 2.0);
}

Playground link

According to the documentation, one would expect this program to output 2.0 foobar. Instead, rustc fails to compile it:

error[[E0308]](https://doc.rust-lang.org/nightly/error-index.html#E0308): mismatched types
   [--> src/main.rs:2:26
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021#)    |
2   |     println!("{2:.*}{}", "foobar", 1, 2.0);
    |     ---------------------^^^^^^^^---------
    |     |                    |
    |     |                    expected `usize`, found `&str`
    |     arguments to this function are incorrect
    |
    = note: expected reference `&usize`
               found reference `&&str`
note: associated function defined here
    = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

After glancing at the implementation, it looks to me like in the asterisk case, the precision is always the next implicit positional argument:

if let Some(end) = self.consume_pos('*') {
// Resolve `CountIsNextParam`.
// We can do this immediately as `position` is resolved later.
let i = self.curarg;
self.curarg += 1;
spec.precision = CountIsParam(i);
spec.precision_span =
Some(self.to_span_index(start).to(self.to_span_index(end + 1)));
} else {

The current implementation determines the argument that will be formatted only after the argument containing the precision. This is the reason why the documentation fits the behavior for implicit positional parameters (like {:.*}). However, this doesn't help in the case of explicit positional parameters (like {42:.*}) and named parameters (like {foobar:.*}) – the precision usually does not come in the input preceding <arg>, as stated by the documentation.

In my opinion, this is not an implementation bug, but a documentation bug – the behavior hasn't changed since at least Rust 1.0, see https://godbolt.org/z/xf61oP6hE. It would probably be unwise to change it now as some crates may depend on it. I'd be happy to open a pull request fixing the documentation – there are a few other things I want to enhance in the std::fmt documentation anyway.

Meta

rustc --version --verbose:

rustc 1.62.0-nightly (18f314e70 2022-04-24)
binary: rustc
commit-hash: 18f314e7027fe7084aaab8620c624a0d7bd29e70
commit-date: 2022-04-24
host: x86_64-unknown-linux-gnu
release: 1.62.0-nightly
LLVM version: 14.0.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions