You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This adds a new configurable lint [disallowed_trait_usage] that lets users forbid using a type via a specific trait interface.
Motivation
A common mistake is using Debug formatting ({:?}) where Display formatting ({}) would be more appropriate, especially for user-facing output like error messages, logs, and GUIs. For example:
// Bad: Debug formatting of errors is ugly and implementation-specificlet err: std::io::Error = std::fs::read("missing.txt").unwrap_err();
log::warn!("Failed to read file: {err:?}");// prints: Failed to read file: Os { code: 2, kind: NotFound, message: "No such file or directory" }// Good: Display formatting is human-readable
log::warn!("Failed to read file: {err}");// prints: Failed to read file: No such file or directory (os error 2)
This applies broadly — paths, errors, and many other types have Debug output that is noisy and not meant for end users. There's no way to systematically catch these with existing lints. The unnecessary_debug_formatting lint only covers OsStr and Path, and can't be extended to arbitrary types or project-specific conventions.
Configuration
Each entry requires a trait field (the trait being disallowed) and either:
type: a concrete type path, or
implements: a trait path — matches any type implementing that trait
# clippy.tomldisallowed-trait-usage = [
# Forbid Debug formatting of specific types:
{ type = "std::path::PathBuf", trait = "std::fmt::Debug", reason = "Use path.display() instead" },
{ type = "std::path::Path", trait = "std::fmt::Debug", reason = "Use path.display() instead" },
# Forbid Debug formatting of ALL types implementing std::error::Error:
{ implements = "std::error::Error", trait = "std::fmt::Debug", reason = "Use Display for errors" },
# Works with primitive types too:
{ type = "i32", trait = "std::fmt::Debug", reason = "Use Display formatting instead" },
# Works with custom types and traits:
{ type = "my_crate::MyStruct", trait = "my_crate::MyTrait", reason = "Use a different approach" },
]
What it detects
Format macro arguments: println!("{:?}", my_path), format!("{path:?}"), write!(buf, "{:?}", x)
Direct trait method calls: my_struct.trait_method()
Works with:
Standard library types and traits (e.g. std::path::PathBuf + std::fmt::Debug)
Primitive types (i32, bool, str, etc.)
Custom user-defined types and traits
All format traits (Debug, Display, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer)
References (automatically peeled)
Trait-bound matching via implements (any type implementing the given trait)
Path validation
Invalid paths produce clear warnings, following the same pattern as disallowed_types/disallowed_methods:
warning: expected a trait, found a struct: `std::string::String` (in `disallowed-trait-usage`)
warning: `std::nonexistent::Foo` does not refer to a reachable type (in `disallowed-trait-usage`)
warning: `type` and `implements` are mutually exclusive (in `disallowed-trait-usage`)
changelog: [disallowed_trait_usage]: new lint that forbids using a configured type (or implementors of a trait) via a configured trait interface
I'm unclear on the value of this lint, though I don't mind people being able to choose to restrict how they write their code. The reason I comment at all is because this is approximate to an extension of an already-existing lint, disallowed_methods, which already had a PR to allow it to apply to trait methods: #12194
I'd like to ask if the trait granularity and a new lint is a better approach than reviving the existing PR to extend the existing lint (which is distinct as it would offer method granularity).
My main motivation is to be able to outlaw uses of Debug formatting for types in my codebase that implements Display. I've made several PRs where I've manually looked for Debug formatting of certain types and replaced them with Display formatting. I'd like to enforce this using clippy. Examples:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
needs-fcpPRs that add, remove, or rename lints and need an FCPS-waiting-on-reviewStatus: Awaiting review from the assignee but also interested parties
5 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New lint:
disallowed_trait_usagedisallowed-trait-usage#15765disallowed_methodson trait impls #8581This adds a new configurable lint [
disallowed_trait_usage] that lets users forbid using a type via a specific trait interface.Motivation
A common mistake is using
Debugformatting ({:?}) whereDisplayformatting ({}) would be more appropriate, especially for user-facing output like error messages, logs, and GUIs. For example:This applies broadly — paths, errors, and many other types have
Debugoutput that is noisy and not meant for end users. There's no way to systematically catch these with existing lints. Theunnecessary_debug_formattinglint only coversOsStrandPath, and can't be extended to arbitrary types or project-specific conventions.Configuration
Each entry requires a
traitfield (the trait being disallowed) and either:type: a concrete type path, orimplements: a trait path — matches any type implementing that traitWhat it detects
println!("{:?}", my_path),format!("{path:?}"),write!(buf, "{:?}", x)my_struct.trait_method()Works with:
std::path::PathBuf+std::fmt::Debug)i32,bool,str, etc.)Debug,Display,Binary,Octal,LowerHex,UpperHex,LowerExp,UpperExp,Pointer)implements(any type implementing the given trait)Path validation
Invalid paths produce clear warnings, following the same pattern as
disallowed_types/disallowed_methods:changelog: [
disallowed_trait_usage]: new lint that forbids using a configured type (or implementors of a trait) via a configured trait interfaceImplemented with a lot of help from Claude Code