rust-lang / rust-lang/rust-clippy

Lint on explicit format specifiers in manual Debug implementation

Open
#10,461 2 comments 2 reactions 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

When implementing std::fmt::Debug by hand, using {:?} or {:#?} for inner fields is usually an unwitting mistake where the author doesn't realise that an explicit format specifier will override user's formatting preferences instead of propagating them unchanged.

Lint Name

manual-fmt-in-debug

Category

correctness, suspicious

Advantage

It will improve custom Debug implementations across the ecosystem to better work with each other, propagating user's preferences throughout types.

Drawbacks

Sometimes user intentionally wants to use compact format for nested fields - e.g. if a custom structure is pretty-printed, but contains raw blob of bytes they want to be printed on one line.

In those cases, user should be able to opt-out via #[allow(...)], but the gut feeling is that those are relatively rare compared to compact format used unwittingly.

Example
#[derive(Debug, Clone, Copy)]
struct A {
    x: u32,
    y: u32,
}

struct BadB(A);

impl std::fmt::Debug for BadB {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<<<{:?}>>>", &self.0) // this should trigger the lint
    }
}

fn main() {
    let a = A {
        x: 10,
        y: 20
    };
    println!("{:#?}", BadB(a));
    println!("{:#X?}", BadB(a));
}

Output:

<<<A { x: 10, y: 20 }>>>
<<<A { x: 10, y: 20 }>>>

Could be written as:

#[derive(Debug, Clone, Copy)]
struct A {
    x: u32,
    y: u32,
}

struct GoodB(A);

impl std::fmt::Debug for GoodB {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("<<<")?;
        self.0.fmt(f)?;
        f.write_str(">>>")?;
        Ok(())
    }
}

fn main() {
    let a = A {
        x: 10,
        y: 20
    };
    println!("{:#?}", GoodB(a));
    println!("{:#X?}", GoodB(a));
}

Output:

<<<A {
    x: 10,
    y: 20,
}>>>
<<<A {
    x: 0xA,
    y: 0x14,
}>>>

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

Begin with the manual-fmt-in-debug requirements and compare the BadB and GoodB Rust examples. Done means explicit {:?} and {:#?} specifiers in manual Debug implementations are diagnosed, while intentionally compact nested formatting can be allowed as described.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
devtools
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.