rust-lang / rust-lang/rust-clippy

Lint when using `format!` or `Vec::join` in `fmt::Display` and `fmt::Debug` implementations

Open
#15,224 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-lint
Dominant language
Rust
Stars
13.5k
Forks
2.2k
Avg merge
2d 10h
Merged PRs (30d)
32

Description

What it does

Within an implementation of core::fmt::<Various>::fmt, it is usually wasteful and unintentional to use format! or Vec::from_iterator + Vec::join and other similar allocating methods.

This lint would warn on such cases to educate the author on ways to prevent allocating in the fmt method.

Similar warnings would be useful for manual implementations of serde::Serialize, but is out of scope for this warning.

Advantage
  • Removes wasteful alloc/free during Debug/Display::fmt impacting performance.
Drawbacks

There are situations where an author may need to allocate an intermediate string before writing to the formatter such as

  • when handling width and padding inputs. In such cases, this lint would be a false positive.
  • or if the underlying writer for the formatter is not well suited to handle short-writes, then the suggested changes may worsen performance.

Additionally, the recommended code is admittedly harder to read.

Example
struct DisplayMySlice<'a>(&'a [ComplexObject]);

impl std::fmt::Display for DisplayMySlice<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "[{}]",
            self.0
                .iter()
                .map(|x| format!("{x}"))
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

Could be written as:

impl std::fmt::Display for DisplayMySlice<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut iter = self.0.iter();
        if let Some(value) = iter.next() {
            write!(f, "[{value}")?;
            for value in iter {
                write!(f, ", {value}")?;
            }
            write!(f, "]")
        } else {
            write!(f, "[]")
        }
    }
}

When iter::Intersperse is stabilized, the recommendation would be to cast the values to &dyn Display and use that instead:

#![feature(iter_intersperse)]
impl std::fmt::Display for DisplayMySlice<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[")?;
        for v in self
            .0
            .iter()
            .map(|v| v as &dyn std::fmt::Display)
            .intersperse(&", ")
        {
            write!(f, "{v}")?;
        }
        write!(f, "]")
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No repository file or test is named. Start from implementations of core::fmt::::fmt and review the format!, Vec::from_iterator, and Vec::join examples; done means identifying wasteful allocations in Display and Debug formatting while excluding the stated serde::Serialize scope and accounting for documented false-positive cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.