Skip to content

Implement default_mismatches_new lint#16531

Open
nyurik wants to merge 3 commits into
rust-lang:masterfrom
nyurik:default_mismatches_new
Open

Implement default_mismatches_new lint#16531
nyurik wants to merge 3 commits into
rust-lang:masterfrom
nyurik:default_mismatches_new

Conversation

@nyurik

@nyurik nyurik commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

View all comments

Re-opening #14234 Implement #14075. Prevents rust-lang/rust#135977.

What it does

If a type has an auto-derived Default trait and a fn new() -> Self, this lint checks if the new() method performs custom logic rather than calling Self::default()

Why is this bad?

Users expect the new() method to be equivalent to default(), so if the Default trait is auto-derived, the new() method should not perform custom logic. Otherwise, there is a risk of different behavior between the two instantiation methods.

Example

#[derive(Default)]
struct MyStruct(i32);
impl MyStruct {
  fn new() -> Self {
    Self(42)
  }
}

Users are unlikely to notice that MyStruct::new() and MyStruct::default() would produce different results. The new() method should use auto-derived default() instead to be consistent:

#[derive(Default)]
struct MyStruct(i32);
impl MyStruct {
  fn new() -> Self {
    Self::default()
  }
}

Alternatively, if the new() method requires non-default initialization, implement a custom Default. This also allows you to mark the new() implementation as const:

struct MyStruct(i32);
impl MyStruct {
  const fn new() -> Self {
    Self(42)
  }
}
impl Default for MyStruct {
  fn default() -> Self {
    Self::new()
  }
}
  • Followed lint naming conventions
  • Added passing UI tests (including committed .stderr file)
  • cargo test passes locally
  • Executed cargo dev update_lints
  • Added lint documentation
  • Run cargo dev fmt

changelog: [default_mismatches_new]: new lint to catch fn new() -> Self method not matching the Default implementation

Notable cases in the wild

  • glob - default() and new() are actually different - likely was a bug that glob team wants to fix (per comments)
  • backtrace - new() creates cache with Vec::with_capacity(), whereas default() uses Vec::default(). Possibly a bug.
  • tracing_core uses Self(()) -- should this be ignored?
  • tracing_subscriber similarly uses Self { _p: () } for struct Identity { _p: () } -- probably better to use ::default()

TODO/TBD

  • Which cases of manual fn new() -> Self implementations are OK? (rather than delegating to Self::default())
    • In case Self is a unit type
  • given an rustc_hir::hir::ImplItem of a fn new() -> Self, get the body of that function
  • verify that the fn body simply does return Self::default(); and no other logic (in all variants like Default::default(), etc.
  • I did a minor modification to clippy_lints/src/default_constructed_unit_structs.rs while trying to understand its logic - I think we may want to have a is_unit_type(ty) utility function?
  • Handle generic types. This should be done in another PR due to complexity. See comment

r? @samueltardieu

See also: non_canonical_partial_ord_impl lint

@rustbot rustbot added needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Feb 7, 2026
@rustbot

rustbot commented Feb 7, 2026

Copy link
Copy Markdown
Collaborator

r? @Jarcho

rustbot has assigned @Jarcho.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: 7 candidates
  • 7 candidates expanded to 7 candidates
  • Random selection from Jarcho, dswij, llogiq, samueltardieu

@rustbot rustbot assigned samueltardieu and unassigned Jarcho Feb 7, 2026
@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

Lintcheck changes for 3e33c84

Lint Added Removed Changed
clippy::default_mismatches_new 12 0 0

This comment will be updated if you push new changes

@ada4a

ada4a commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Alternatively, if the new() method requires non-default initialization, implement a custom Default. This also allows you to mark the new() implementation as const:

Some of the examples from Lintcheck seem to match this pattern where new() apparently repeats default() just to be able to be const. Maybe in this case the suggestion should need to be changed to, or extended to include, "in order to keep new() as a const fn, implement a custom Default::default() that calls Self::new()"

@samueltardieu

Copy link
Copy Markdown
Member

Some of the examples from Lintcheck seem to match this pattern where new() apparently repeats default() just to be able to be const. Maybe in this case the suggestion should need to be changed to, or extended to include, "in order to keep new() as a const fn, implement a custom Default::default() that calls Self::new()"

Yes, I think we should not lint in this case. There will be a time where it will be possible to have T::default() be const (this is already possible using the const_default and const_trait_impl features), but until then we can't tell people not to auto-derive Default.

@samueltardieu samueltardieu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass at the review.

The part about default_constructed_unit_structs seems independent. If this is the case, can you isolate it and make a new PR explaining what it fixes? (you can assign it to me)

Also, it would be beneficial to cleanup the content of new_without_default.rs in a first commit. It has been written a long time ago, and, for example, we now import struct names from rustc_hir directly instead of using hir::StructName (for example for hir::ExprKind::Ret, we now use ExprKind::Ret).

Keep in mind that those lints, including the new proposed one, are warned by default, so we have to be extra careful in what we put in there.

View changes since this review

Comment thread clippy_lints/src/new_vs_default.rs
Comment thread clippy_lints/src/new_without_default.rs Outdated
Comment thread clippy_lints/src/new_without_default.rs Outdated
Comment thread clippy_lints/src/new_without_default.rs Outdated
Comment thread clippy_lints/src/new_without_default.rs Outdated
Comment thread clippy_lints/src/new_without_default.rs Outdated
Comment thread clippy_lints/src/new_without_default.rs
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Feb 21, 2026
@rustbot

rustbot commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@nyurik nyurik force-pushed the default_mismatches_new branch from f656707 to c0e168f Compare February 28, 2026 19:50
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@nyurik nyurik force-pushed the default_mismatches_new branch from c0e168f to 2979df4 Compare April 17, 2026 02:14
@rustbot

This comment has been minimized.

@nyurik nyurik force-pushed the default_mismatches_new branch 2 times, most recently from bf52143 to 98635f0 Compare April 17, 2026 03:26
@nyurik nyurik force-pushed the default_mismatches_new branch from 98635f0 to c705c93 Compare April 17, 2026 03:37
@nyurik nyurik requested a review from samueltardieu April 17, 2026 03:40
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Apr 17, 2026
@rustbot

This comment has been minimized.

@nyurik nyurik force-pushed the default_mismatches_new branch from c705c93 to cd78473 Compare May 1, 2026 15:32
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

Move is_unit_struct into clippy_utils to use it in
the new lint default_mismatches_new
@nyurik nyurik force-pushed the default_mismatches_new branch from cd78473 to dc36bbc Compare June 1, 2026 16:16
@rustbot

rustbot commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@nyurik

nyurik commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

r? @ada4a

@rustbot rustbot assigned ada4a and unassigned samueltardieu Jun 1, 2026
@rustbot

rustbot commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

ada4a is not on the review rotation at the moment.
They may take a while to respond.

@nyurik

nyurik commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

hmm, not sure how to re-request a review...

@rustbot reroll

@rustbot

rustbot commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Error: Parsing assign command in comment failed: specify user to assign to (if you want to reroll without a specific reviewer in mind, use @rustbot reroll)

Please file an issue on GitHub at triagebot if there's a problem with this bot, or reach out on #triagebot on Zulip.

@rustbot rustbot assigned llogiq and unassigned ada4a Jun 5, 2026

@samueltardieu samueltardieu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay.

r? samueltardieu
@rustbot label lint-nominated

View changes since this review

@@ -1,7 +1,9 @@
#![allow(
clippy::missing_safety_doc,
dead_code,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dead_code,

@rustbot rustbot assigned samueltardieu and unassigned llogiq Jun 5, 2026
@rustbot rustbot added lint-nominated Create an FCP-thread on Zulip for this PR S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jun 5, 2026
@rustbot

rustbot commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

This lint has been nominated for inclusion.

A FCP topic has been created on Zulip.

Comment on lines +373 to +378
diag.help(
"when a type derives `Default`, users expect `new()` and `default()` to be equivalent. \
Consider delegating to `Self::default()` for consistency, or rename `new` if the \
behavior is intentionally different",
);
diag.span_suggestion(span, "use", "Self::default()", Applicability::MaybeIncorrect);

@ada4a ada4a Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kudos for providing a nice and descriptive help message:) I think I'd split it up though:

  • the first sentence should probably be a note, as it gives more context for the lint, not help. Probably even diag.note_once
  • "consider delegating to Self::default() for consistency" should be made the message of the existing suggestion (instead of "use"), as it describes that suggestion specifically
  • finally, "or rename new if the behavior is intentionally different" will remain as an independent help message, as it offers an alternative fix

View changes since the review

Comment on lines +364 to +365
let self_ty_fmt = self_ty.to_string();
let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt);

@ada4a ada4a Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's be a bit lazier

Suggested change
let self_ty_fmt = self_ty.to_string();
let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt);
let self_type_snip = snippet_opt(cx, impl_self_ty.span).unwrap_or_else(|| self_ty.to_string());

View changes since the review

Comment on lines +200 to +203
if sig.header.constness == Constness::Const {
continue;
}
if let ExprKind::Block(block, _) = cx.tcx.hir_body(body_id).value.kind

@ada4a ada4a Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since we already have a let chain, let's pull this into it

Suggested change
if sig.header.constness == Constness::Const {
continue;
}
if let ExprKind::Block(block, _) = cx.tcx.hir_body(body_id).value.kind
if sig.header.constness != Constness::Const
&& let ExprKind::Block(block, _) = cx.tcx.hir_body(body_id).value.kind

View changes since the review

Comment on lines +4 to +10
LL | fn new() -> Self {
| _______________________^
LL | |/
LL | || Self { value: 0 }
| ||_________________________- help: use: `Self::default()`
LL | | }
| |______^

@ada4a ada4a Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this mishmash of arrows is pretty confusing imo -- let's move the suggestion into its own block by using span_suggestion_verbose

View changes since the review

ada4a and others added 2 commits June 6, 2026 15:31
Mostly to simplify next commit's diff
If a type has an auto-derived `Default` trait and a `fn new() -> Self`,
this lint checks if the `new()` method performs custom logic rather
than simply calling the `default()` method.

Users expect the `new()` method to be equivalent to `default()`,
so if the `Default` trait is auto-derived, the `new()` method should
not perform custom logic.  Otherwise, there is a risk of different
behavior between the two instantiation methods.

```rust
struct MyStruct(i32);
impl MyStruct {
  fn new() -> Self {
    Self(42)
  }
}
```

Users are unlikely to notice that `MyStruct::new()` and `MyStruct::default()` would produce
different results. The `new()` method should use auto-derived `default()` instead to be consistent:

```rust
struct MyStruct(i32);
impl MyStruct {
  fn new() -> Self {
    Self::default()
  }
}
```

Alternatively, if the `new()` method requires a non-default initialization, implement a custom `Default`.
This also allows you to mark the `new()` implementation as `const`:

```rust
struct MyStruct(i32);
impl MyStruct {
  const fn new() -> Self {
    Self(42)
  }
}
impl Default for MyStruct {
  fn default() -> Self {
    Self::new()
  }
}
```
@ada4a

ada4a commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

@nyurik I've created a branch which splits off the file rename into a separate commit, and is otherwise identical to your branch. You should be able to make use of it as follows:

# fetch my branch
git fetch https://github.com/ada4a/rust-clippy default_mismatches_new:ada4a/default_mismatches_new

# (optional) rebase your work, if any, on top of the new branch.
# requires you to first switch to your branch if you haven't already
git switch default_mismatches_new
git rebase ada4a/default_mismatches_new

# remove my branch again
git branch -D ada4a/default_mismatches_new

@nyurik

nyurik commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

@ada4a thx, rebased

@rustbot

rustbot commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (possibly #17231) made this pull request unmergeable. Please resolve the merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lint-nominated Create an FCP-thread on Zulip for this PR needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants