From 1778e5c1a771a4047b03d2c6a389be206252f2a5 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 16:52:34 +0100 Subject: [PATCH 01/30] Allow `#[ignore(Trait)]` on field to ignore it when deriving `Trait` --- text/0000-ignore-derive.md | 624 +++++++++++++++++++++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 text/0000-ignore-derive.md diff --git a/text/0000-ignore-derive.md b/text/0000-ignore-derive.md new file mode 100644 index 00000000000..41c8e107365 --- /dev/null +++ b/text/0000-ignore-derive.md @@ -0,0 +1,624 @@ +- Feature Name: `ignore_derive` +- Start Date: 2025-10-06 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +The `#[ignore]` attribute can now be applied to fields. Its purpose is to tell derive macros to skip the field when generating code. + +```rust +#[derive(Clone, PartialEq, Eq, std::hash::Hash)] +struct User { + #[ignore(PartialEq, std::hash::Hash)] + // ^^^^^^^^^ ^^^^^^^^^^^^^^^ + // traits that will ignore this field + name: String, + #[ignore(PartialEq, std::hash::Hash)] + age: u8, + id: u64 +} +``` + +For the above struct `User`, derives `PartialEq` and `Hash` will ignore the `name` and `age` fileds. Code like this is generated: + +```rust +impl Clone for User { + fn clone(&self) -> User { + User { + name: self.name.clone(), + age: self.age.clone(), + id: self.id.clone(), + } + } +} + +impl PartialEq for User { + fn eq(&self, other: &User) -> bool { + self.id == other.id + } +} + +impl Eq for User {} + +impl std::hash::Hash for User { + fn hash(&self, state: &mut H) -> () { + std::hash::Hash::hash(&self.id, state) + } +} +``` + +# Motivation +[motivation]: #motivation + +It's common to want to exclude one or more fields when deriving traits such as `Debug` or `Deserialize`. +To do this, you currently need to implement completely abandon the `derive` and instead implement the trait manually. + +Manually implementing the trait is much more error-prone than letting the derive do it for you. +For example, when a new field is added, it's possible to forget to change all of your implementations. + +This is why it's idiomatic to instead `derive` traits when it's possible to do so. But this deriving isn't possible if you need to ignore a field. +Common use-cases of ignoring fields when implementing traits include: + +- Types that are high-level wrappers around other types, which may include some additional metadata but ultimately delegate most trait + implementations to the inner type. An example is an identifier in programming languages: + + ```rust + /// Identifier for a variable, function or trait + #[derive(PartialEq, Eq)] + struct Ident { + /// Location of the identifier in the source code + span: Span, + /// Value of the identifier + #[ignore(PartialEq, Eq)] + value: String + } + ``` + +- Minor improvements such as skipping irrelevant fields in a `Debug` derive (like fields of type `PhantomData`) derive will become + easy enough for people to just do. Currently, the effort required to maintain a manual implementation is too high - + so people just don't bother with it in most cases. + +- Security: Data with sensitive fields like AWS s3 `access_key_id` and `secret_access_key` that **must** be skipped for `Debug` implementations is forced to be implemented manually - leading to a significant increase in boilerplate + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +When using `#[derive]`, you can apply `#[ignore]` to fields: + +```rust +#[derive(Clone, PartialEq, Eq, std::hash::Hash)] +struct User { + #[ignore(PartialEq, std::hash::Hash)] + name: String, + #[ignore(PartialEq, std::hash::Hash)] + age: u8, + id: u64 +} +``` + +The `#[ignore]` receives paths to a subset of the derive macros applied to the item. + +It is invalid for `#[ignore]` to receive a derive that isn't applied to the current item: + +```rust +#[derive(Clone)] +struct Foo { + #[ignore(Clone, PartialEq)] + foo: String, +} +``` + +In the above example, `Foo` derives `Clone` but not `PartialEq` - so passing `PartialEq` to `ignore` is disallowed. + +## From the perspective of a `derive` macro + +When a derive macro such as `#[derive(std::hash::Hash)]` is applied to an item like a `struct`: + +- Any `#[ignore]` attributes that mention the derive itself, in this case `std::hash::Hash`, will be a part of the `TokenStream` + that the `derive` macro receives - **with the list of derives removed**. The derive macro has no idea what other derives ignore + this field, it just knows that it should ignore it. + + **Example:** `std::hash::Hash` will see `#[ignore] field: ()` when the input contains `#[ignore(std::hash::Hash, Clone)] field: ()` + +- If the `#[ignore]` attribute **does not** mention the derive, then the attribute is removed completely from the macro's input `TokenStream`. + The derive macro doesn't know that other derives ignore this field. + + **Example:** `Clone` will see just `field: ()` when the input contains `#[ignore(std::hash::Hash, Clone)] field: ()` + +### Example + +In the below example: + +- `Clone` will ignore fields `bar` and `baz` +- `std::hash::Hash` will ignore fields `foo` and `baz` + +```rust +#[derive(std::hash::Hash, Clone)] +struct Foo { + #[ignore(std::hash::Hash)] + foo: (), + #[ignore(Clone)] + bar: (), + #[ignore(std::hash::Hash, Clone)] + baz: (), + quux: () +} +``` + +`std::hash::Hash` receives this `TokenStream`: + +```rust +struct Foo { + #[ignore] + foo: (), + bar: (), + #[ignore] + baz: (), + quux: () +} +``` + +Explanation: + +- The `#[ignore]` applied to `foo` **contains** `std::hash::Hash` +- The `#[ignore]` applied to `bar` **does NOT contain** `std::hash::Hash` +- The `#[ignore]` applied to `baz` **contains** `std::hash::Hash` +- There is no `#[ignore]` applied to `quux` + +The `#[ignore]` attribute is included for `foo` and `baz` in `std::hash::Hash`'s input `TokenStream` + +Then it's up to the `std::hash::Hash` macro on how exactly it wants to use the `#[ignore]` attribute. + +- In the common case, it will exclude `foo` and `baz` from the generated `std::hash::Hash` impl +- `std::hash::Hash` is allowed to ignore existence of the attribute. +- It could emit a `compile_error!("`#[ignore]` is not supported for this derive")` + or, in the future, a [diagnostic](https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html) + +## Standard library macros that support `#[ignore]` + +The following standard library traits support `#[ignore]`: + +- `PartialEq` +- `PartialOrd` +- `Ord` +- `Hash` +- `Debug` + +## How this impacts code maintainability + +Given a `Var` like this: + +```rust +#[derive(Clone)] +pub struct Var { + pub ns: Symbol, + pub sym: Symbol, + meta: RefCell, + pub root: RefCell>, + _phantom: PhantomData +} +``` + +You want to implement: + +- `PartialEq` and `Hash` such that only `ns` and `sym` fields are hashes and compared +- `Debug` such that it skips the `_phantom` field + +### Without `#[ignore]` on fields + +you'd need to implement those 3 traits manually: + +```rust +#[derive(Clone)] +pub struct Var { + pub ns: Symbol, + pub sym: Symbol, + meta: RefCell, + pub root: RefCell>, + _phantom: PhantomData +} + +impl fmt::Debug for Var { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Var") + .field("ns", &self.ns) + .field("sym", &self.sym) + .field("meta", &self.meta) + .field("root", &self.root) + .finish() + } +} + +impl PartialEq for Var { + fn eq(&self, other: &Self) -> bool { + self.ns == other.ns && self.sym == other.sym + } +} + +impl Hash for Var { + fn hash(&self, state: &mut H) { + (&self.ns, &self.sym).hash(state); + } +} +``` + +Notes: + +- It is logically incorrect for `Hash` and `PartialEq` implementations to differ, so you must remember to keep them in sync if `Var` changes +- You must remember to update the string names of the `Debug` if you ever rename the fields or `Var` itself + +### With `#[ignore]` + +```rust +#[derive(Clone, fmt::Debug, PartialEq, Hash)] +pub struct Var { + pub ns: Symbol, + pub sym: Symbol, + #[ignore(PartialEq, Hash)] + meta: RefCell, + #[ignore(PartialEq, Hash)] + pub root: RefCell>, + #[ignore(PartialEq, Hash)] + #[ignore(fmt::Debug)] + _phantom: PhantomData +} +``` + +Note: Multiple `#[ignore]` attributes can apply to the same field, which is the same as writing each argument to `ignore` in a single attribute. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The `#[ignore]` attribute now *also* applies to: + +- Fields of `struct`s +- Fields of enum variarnts +- Fields of `union`s + +Notes: + +- Fields can be either named or unnamed. +- When applied to fields, `#[ignore]` takes a list of [`SimplePath`](https://doc.rust-lang.org/reference/paths.html#simple-paths)s separated by comma, + with an optional trailing comma, e.g. `#[ignore(Foo, Bar,)]`. +- The list is allowed to be empty: `#[ignore()]` but it **must** exist. +- Multiple `#[ignore]` attributes on a field are allowed to appear in a row, in which case their list of paths merges into a single `#[ignore]`. + The derive macro still just receives a single `#[ignore]` if it's present in the list of paths, no matter how many `#[ignore]` attributes were used on the field. + +```rust +#[derive(Foo)] +struct NamedFields { + #[ignore(Foo)] + ignored: () +} + +#[derive(Foo)] +struct UnnamedFields(#[ignore(Foo)] ()); + +#[derive(Foo)] +enum Enum { + NamedFields { + #[ignore(Foo)] + ignored: () + }, + UnnamedFields(#[ignore(Foo)] ()) +} + +#[derive(Foo)] +union Union { + #[ignore(Foo)] + ignored: () +} +``` + +### Name resolution + +Paths supplied to `#[ignore]` must resolve to a derive macro applied to the current item. + +```rust +use std::hash::Hash as RenamedHash; + +#[derive(std::hash::Hash)] +struct Foo { + #[ignore(RenamedHash)] + ignored: () +} +``` + +The above works, because `RenamedHash` *is* `std::hash::Hash`. + +### Unknown derive + +A path supplied to `ignore` that does not apply to the current derive, for example: + +```rust +#[derive(Clone)] +struct Foo { + #[ignore(PartialEq)] + ignored: () +} +``` + +Is a compile error. + +### Duplicate derive + +The list of paths passed to `ignore` is not allowed to contain more than 1 of the same path: + +```rust +#[derive(Clone)] +struct Foo { + #[ignore(Clone, ::core::clone::Clone)] + ignored: () +} +``` + +Both of the above generate compile errors. + +## Inside the macro + +The `#[ignore]` attribute(s) for a particular derive macro `Foo` applied to the current item will do one of 2 things +**once they become an input `TokenStream` to the derive macro `Foo`**: + +- If the list of paths includes the derive macro `Foo` itself, then an `#[ignore]` attribute **without** any arguments is applied to the field +- If the list of paths to `ignore` does not name the the derive macro, then the `ignore` attribute is fully removed. + +## Spans + +Because input to the macro is different from what it is in the source code, we have to talk about +the `Span` that the derive macros see. Derives macros that don't support `#[ignore]` will want to report an error about it, +and they can do so by using span of the `ignore` identifier. + +Given the following struct: + +```rust +#[derive(PartialEq, Debug, std::hash::Hash)] +struct Foo { + #[ignore(std::hash::Hash)] + #[ignore(PartialEq, Debug)] + ignored: () +} +``` + +Each of `std::hash::Hash`, `PartialEq`, and `Debug` will receive the following input: + +```rust +#[derive(PartialEq, Debug, std::hash::Hash)] +struct Foo { + #[ignore] + ignored: () +} +``` + +The span of `#[ignore]` attribute received by derive macro `Debug` will be the same as the path `Debug` as *originally* passed to the +`#[ignore(Debug)]` attribute. Span is indicated with `^^^^` in the following examples: + +- `std::hash::Hash`: + + ```rust + #[derive(PartialEq, Debug, std::hash::Hash)] + struct Foo { + #[ignore(std::hash::Hash)] + ^^^^^^^^^^^^^^^ + #[ignore(PartialEq, Debug)] + ignored: () + } + ``` + +- `PartialEq`: + + ```rust + #[derive(PartialEq, Debug, std::hash::Hash)] + struct Foo { + #[ignore(std::hash::Hash)] + #[ignore(PartialEq, Debug)] + ^^^^^^^^^ + ignored: () + } + ``` + +- `Debug`: + + ```rust + #[derive(PartialEq, Debug, std::hash::Hash)] + struct Foo { + #[ignore(std::hash::Hash)] + #[ignore(PartialEq, Debug)] + ^^^^^ + ignored: () + } + ``` + +## New lints + +This RFC additionally proposes to add 2 new deny-by-default lints: + +- Types that implement `Eq` with fields that ignore **just one of** `Hash` or `PartialEq` issue a lint, + because types `k1` and `k2` implementing `Eq` and `Hash` are expected to follow the property: + + ``` + k1 == k2 -> hash(k1) == hash(k2) + ``` + + Violating this property is a logic error, so it would be incorrect to `#[ignore]` only 1 of those traits. + +- Types with fields that ignore **just one of** `PartialEq` or `PartialOrd` issue a lint, + because it is logically incorrect for the implementations to differ. + See the [documentation](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) for details. + +# Drawbacks +[drawbacks]: #drawbacks + +It overloads `ignore` to mean 2 different things, as it currently has 1 meaning: functions marked with `#[test]` will be ignored from the test suite + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +There was an [attempt](https://github.com/rust-lang/rust/pull/121053) to implement this feature with the `#[skip]` attribute before. +However, this lead to [unacceptable breaking changes](https://github.com/rust-lang/libs-team/issues/334#issuecomment-2183774372): + +> To give an update (at long last), the crater report did confirm my suspicion. We have 4 confirmed root regressions: [1], [2], [3], [4], [4.1], [4.2]. + +Considering the breakage would compromise Rust's stabiligy guarantees, a different design is required: + +> Given the crater results showing compatibility hazards, it sounds like this is going to need a design and lang RFC for an approach that avoids those. + +This is that RFC. It proposes a design that **avoids** any breaking changes, by re-using the conveniently named `ignore` attribute for a new purpose. + +## What if someone already has a custom attribute macro named `ignore`? + +Impossible. + +The `#[skip]` built-in attribute could not be used because even the feature gate would break existing code from compiling. +In this RFC, the `#[ignore]` attribute avoids that. +This attribute is already built-in, and people cannot apply an attribute macro named `ignore` as that would be ambiguous: + +```rust +use derive as ignore; + +struct Foo { + #[ignore] + hello: () +} +``` + +The above yields an ambiguity error: + +``` +error[E0659]: `ignore` is ambiguous + --> src/main.rs:4:7 + | +4 | #[ignore] + | ^^^^^^ ambiguous name + | + = note: ambiguous because of a name conflict with a builtin attribute + = note: `ignore` could refer to a built-in attribute +note: `ignore` could also refer to the attribute macro imported here + --> src/main.rs:1:5 + | +1 | use derive as ignore; + | ^^^^^^^^^^^^^^^^ + = help: use `crate::ignore` to refer to this attribute macro unambiguously +``` + +You **must** use the attribute as `#[ignore()]` where inside of the parentheses we can specify a list of paths. +Because today `#[ignore]` is already valid on fields: + +``` +warning: `#[ignore]` only has an effect on functions + --> src/main.rs:2:5 + | +2 | #[ignore] + | ^^^^^^^^^ + | + = note: `#[warn(unused_attributes)]` on by default +``` + +It could be possible to implement `#[ignore]` on fields over an edition, maybe. This is discussed in the "Future Possibilities" section. + +By explicitly **requiring** the list of arguments with parentheses, we can just feature-gate that syntax - which is currently invalid with a deny-by-default lint: + +``` +error: valid forms for the attribute are `#[ignore]` and `#[ignore = "reason"]` + --> src/main.rs:2:5 + | +2 | #[ignore()] + | ^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #57571 + = note: `#[deny(ill_formed_attribute_input)]` on by default +``` + +Once this RFC is implemented, the above deny-by-default lint is promoted into a hard error on the stable channel. +This should be acceptable as it's unlikely someone uses this invalid syntax today - which doesn't do anything. +Even if so, the lint clearly states that it will become an error in a future release. +The lint was implemented in January 2019. See the [tracking issue](https://github.com/rust-lang/rust/issues/57571). +It has been almost 6 years since it became deny-by-default. +It should be fine to promote it into a hard error as that's what this RFC would require for the feature-gate + +## Why not choose a new name + +As seen with the `#[skip]` attribute attempt, it is likely to lead to breakages when we try to create a new built-in attribute for this. +The `ignore` keyword is very convenient for us, because it lets us implement this feature using an understandable keyword +while not compromising on the stability guarantees. + +## How about `#[debug(ignore)]` or something like that? + +That's how the ecosystem works at the moment. Crates such as `serde`, `clap` each have their own attributes for ignoring a field: `#[serde(skip)]`. + +There is also the crate `derivative` which implements the ignore functionality for standard library traits: + +```rust +#[derive(Derivative)] +#[derivative(Debug)] +struct Foo { + foo: u8, + #[derivative(Debug="ignore")] + bar: String, +} +``` + +There are numerous disadvantages to not having a single attribute in the standard library for skipping fields when deriving: + +- Blocks standard library derives like `Hash`, `Ord`, `PartialOrd`, `Eq`, `PartialEq` from ignoring fields, because + if adding the 1 `skip` attribute lead to so much breakage imagine if we added 5 new attributes +- People have to learn each crate's own way of skipping fields, if it exists at all. With `#[ignore]` on fields, the user can instead use the same syntax + for every derive macro. Derives that do not support `#[ignore]` attribute will be encouraged to emit a compile error to helpfully let the user know. +- Standardizes syntax and makes reading Rust code more predictable. +- Multiple `#[foo(ignore)]`, `#[bar(ignore)]` attributes each take up a separate line. A single `#[ignore(Foo, Bar)]` can instead nicely fit on a single line. +- **Compile times.** If we have a separate crate re-implement the standard library `Hash` derives and others, then the compile times will be much worse since + they'll require additional dependencies like `syn` and miss out on the optimizations that can be gained with a derive macro that is + included in the standard library + +## What is the impact of not doing this? + +- Less standardization in the ecosystem. Each crate can have its own way to do things. +- People are more likely to write buggy or incorrect code because they forget to update manual implementations of traits like `PartialEq` + that only exist because they needed to exclude a field +- A lot of boilerplate that is usually taken care of by derive macros + +# Prior art +[prior-art]: #prior-art + +There are no other languages with something like derive macros that directly have this feature built-in + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +None + +# Future possibilities +[future-possibilities]: #future-possibilities + +## `#[ignore]` without a list of paths is visible to all macros + +It could be possible to allow just `#[ignore]` on fields without a list of paths - +which would make `#[ignore]` visible to every macro. For example, make this: + +```rust +#[derive(Eq, std::hash::Hash, MyCustomDerive)] +struct Foo { + foo: String, + #[ignore] + bar: u32 +} +``` + +Equivalent to the following: + +```rust +#[derive(Eq, std::hash::Hash, MyCustomDerive)] +struct Foo { + foo: String, + #[ignore(Eq, std::hash::Hash, MyCustomDerive)] + bar: u32 +} +``` + +If desired, such a change will be possible to make in the future, but it is not part of this RFC because the first code block **already compiles** (although with a warning - `ignore only has an effect on functions`). + +## `#[ignore]` on enum variants + +There is no known reason why this would be useful, but it could be added in the future if needed From ddfbc79c8132363b3562de2f63a2abcb88421952 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 16:54:07 +0100 Subject: [PATCH 02/30] Add line breaks --- text/0000-ignore-derive.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/0000-ignore-derive.md b/text/0000-ignore-derive.md index 41c8e107365..11b61cd0747 100644 --- a/text/0000-ignore-derive.md +++ b/text/0000-ignore-derive.md @@ -6,7 +6,8 @@ # Summary [summary]: #summary -The `#[ignore]` attribute can now be applied to fields. Its purpose is to tell derive macros to skip the field when generating code. +The `#[ignore]` attribute can now be applied to fields. +Its purpose is to tell derive macros to skip the field when generating code. ```rust #[derive(Clone, PartialEq, Eq, std::hash::Hash)] @@ -21,7 +22,8 @@ struct User { } ``` -For the above struct `User`, derives `PartialEq` and `Hash` will ignore the `name` and `age` fileds. Code like this is generated: +For the above struct `User`, derives `PartialEq` and `Hash` will ignore the `name` and `age` fileds. +Code like this is generated: ```rust impl Clone for User { From dafa97c6e900ce74c8daad87f96485de5d671d31 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 16:55:09 +0100 Subject: [PATCH 03/30] Spelling mistake --- text/0000-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-ignore-derive.md b/text/0000-ignore-derive.md index 11b61cd0747..c7ed42eb76f 100644 --- a/text/0000-ignore-derive.md +++ b/text/0000-ignore-derive.md @@ -55,7 +55,7 @@ impl std::hash::Hash for User { [motivation]: #motivation It's common to want to exclude one or more fields when deriving traits such as `Debug` or `Deserialize`. -To do this, you currently need to implement completely abandon the `derive` and instead implement the trait manually. +To do this, you currently need to completely abandon the `derive` and instead implement the trait manually. Manually implementing the trait is much more error-prone than letting the derive do it for you. For example, when a new field is added, it's possible to forget to change all of your implementations. From 17f0fbb645de063f089dac114162148c14a5b15a Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 16:57:00 +0100 Subject: [PATCH 04/30] Rename file --- text/{0000-ignore-derive.md => 3869-ignore-derive.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-ignore-derive.md => 3869-ignore-derive.md} (100%) diff --git a/text/0000-ignore-derive.md b/text/3869-ignore-derive.md similarity index 100% rename from text/0000-ignore-derive.md rename to text/3869-ignore-derive.md From 62e14e697ae0c948629cbf15e69b3963eddf864f Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 16:58:02 +0100 Subject: [PATCH 05/30] Add number of this PR --- text/3869-ignore-derive.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index c7ed42eb76f..bf5276b25e1 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -1,7 +1,7 @@ - Feature Name: `ignore_derive` - Start Date: 2025-10-06 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- RFC PR: [rust-lang/rfcs#3869](https://github.com/rust-lang/rfcs/pull/3869) +- Rust Issue: [rust-lang/rust#3869](https://github.com/rust-lang/rust/issues/3869) # Summary [summary]: #summary From b7c85fff7e4f3319a676e5bca38318141f83a428 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:00:26 +0100 Subject: [PATCH 06/30] Fix the `ignore` on `Ident` in wrong position --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index bf5276b25e1..e7ec28e0564 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -71,9 +71,9 @@ Common use-cases of ignoring fields when implementing traits include: #[derive(PartialEq, Eq)] struct Ident { /// Location of the identifier in the source code + #[ignore(PartialEq, Eq)] span: Span, /// Value of the identifier - #[ignore(PartialEq, Eq)] value: String } ``` From 0665175695c46bb0028727fdde90fb7a55bdbf49 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:02:33 +0100 Subject: [PATCH 07/30] Add extra backtick --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index e7ec28e0564..47242b07918 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -175,7 +175,7 @@ Then it's up to the `std::hash::Hash` macro on how exactly it wants to use the ` - In the common case, it will exclude `foo` and `baz` from the generated `std::hash::Hash` impl - `std::hash::Hash` is allowed to ignore existence of the attribute. -- It could emit a `compile_error!("`#[ignore]` is not supported for this derive")` +- It could emit a `` compile_error!("`#[ignore]` is not supported for this derive") `` or, in the future, a [diagnostic](https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html) ## Standard library macros that support `#[ignore]` From 7fc35350ef61c0e5a85794cc0b05419ff186c20b Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:04:07 +0100 Subject: [PATCH 08/30] Clarify list --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 47242b07918..d5ab9a40f35 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -284,7 +284,7 @@ Notes: - Fields can be either named or unnamed. - When applied to fields, `#[ignore]` takes a list of [`SimplePath`](https://doc.rust-lang.org/reference/paths.html#simple-paths)s separated by comma, with an optional trailing comma, e.g. `#[ignore(Foo, Bar,)]`. -- The list is allowed to be empty: `#[ignore()]` but it **must** exist. +- The list is allowed to be empty: `#[ignore()]` but it **must** exist. Just `#[ignore]` without the `()` is not allowed. - Multiple `#[ignore]` attributes on a field are allowed to appear in a row, in which case their list of paths merges into a single `#[ignore]`. The derive macro still just receives a single `#[ignore]` if it's present in the list of paths, no matter how many `#[ignore]` attributes were used on the field. From 8f640759fe49210104b939370e53ff9d27294d91 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:04:47 +0100 Subject: [PATCH 09/30] Remove incorrect sentence --- text/3869-ignore-derive.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index d5ab9a40f35..a463cd8298c 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -356,8 +356,6 @@ struct Foo { } ``` -Both of the above generate compile errors. - ## Inside the macro The `#[ignore]` attribute(s) for a particular derive macro `Foo` applied to the current item will do one of 2 things From e82ad51e925954c30779ef108aac5ddb422eb090 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:05:12 +0100 Subject: [PATCH 10/30] Simplify sentence --- text/3869-ignore-derive.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index a463cd8298c..a2e324c3921 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -332,7 +332,7 @@ The above works, because `RenamedHash` *is* `std::hash::Hash`. ### Unknown derive -A path supplied to `ignore` that does not apply to the current derive, for example: +A path supplied to `ignore` that does not apply to the current derive is disallowed. For example: ```rust #[derive(Clone)] @@ -342,8 +342,6 @@ struct Foo { } ``` -Is a compile error. - ### Duplicate derive The list of paths passed to `ignore` is not allowed to contain more than 1 of the same path: From fb519f0f4efb472687fd151077a05b528c2ec40f Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:07:02 +0100 Subject: [PATCH 11/30] Clarify that the span differs --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index a2e324c3921..67d834ffe65 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -379,7 +379,7 @@ struct Foo { } ``` -Each of `std::hash::Hash`, `PartialEq`, and `Debug` will receive the following input: +Each of `std::hash::Hash`, `PartialEq`, and `Debug` will receive the following input, except that span of the `#[ignore]` will differ: ```rust #[derive(PartialEq, Debug, std::hash::Hash)] From 6dc5c613e90e395ae4773af57baee2b123662280 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 17:09:05 +0100 Subject: [PATCH 12/30] Clarify possible bare `#[ignore]` --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 67d834ffe65..8421a65900f 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -513,7 +513,7 @@ warning: `#[ignore]` only has an effect on functions = note: `#[warn(unused_attributes)]` on by default ``` -It could be possible to implement `#[ignore]` on fields over an edition, maybe. This is discussed in the "Future Possibilities" section. +It could be possible to implement `#[ignore]` (without a list of paths) on fields over an edition, maybe. This is discussed in the "Future Possibilities" section. By explicitly **requiring** the list of arguments with parentheses, we can just feature-gate that syntax - which is currently invalid with a deny-by-default lint: From d993eeb6e8a7357b8b381f7312734b010a3a5aa2 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 18:39:56 +0100 Subject: [PATCH 13/30] Remove incorrectly placed `Clone` Co-authored-by: Jacob Lifshay --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 8421a65900f..697b38eeaa1 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -127,7 +127,7 @@ When a derive macro such as `#[derive(std::hash::Hash)]` is applied to an item l - If the `#[ignore]` attribute **does not** mention the derive, then the attribute is removed completely from the macro's input `TokenStream`. The derive macro doesn't know that other derives ignore this field. - **Example:** `Clone` will see just `field: ()` when the input contains `#[ignore(std::hash::Hash, Clone)] field: ()` + **Example:** `Clone` will see just `field: ()` when the input contains `#[ignore(std::hash::Hash)] field: ()` ### Example From 94c2230d33464fe3ee9144bcc0ef9016662c1c6c Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 18:45:57 +0100 Subject: [PATCH 14/30] Add `Ord` to the 2nd lint --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 697b38eeaa1..76c79f6f1d0 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -441,7 +441,7 @@ This RFC additionally proposes to add 2 new deny-by-default lints: Violating this property is a logic error, so it would be incorrect to `#[ignore]` only 1 of those traits. -- Types with fields that ignore **just one of** `PartialEq` or `PartialOrd` issue a lint, +- Types with fields that ignore **just one, or just two of** `PartialEq`, `PartialOrd` and `Ord` issue a lint, because it is logically incorrect for the implementations to differ. See the [documentation](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) for details. From ea94f4a9e52c57c9da0a660f45e2963020a3c869 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Mon, 6 Oct 2025 18:51:08 +0100 Subject: [PATCH 15/30] Allow `#[ignore]` on enum variants --- text/3869-ignore-derive.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 76c79f6f1d0..9142ab2d67d 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -114,6 +114,20 @@ struct Foo { In the above example, `Foo` derives `Clone` but not `PartialEq` - so passing `PartialEq` to `ignore` is disallowed. +## Variants + +You can also apply `#[derive]` to enum variants, too: + +```rust +#[derive(Serialize, Deserialize, Debug)] +enum Status { + Active, + Inactive, + #[ignore(Serialize, Deserialize)] + Unknown, +} +``` + ## From the perspective of a `derive` macro When a derive macro such as `#[derive(std::hash::Hash)]` is applied to an item like a `struct`: @@ -278,6 +292,7 @@ The `#[ignore]` attribute now *also* applies to: - Fields of `struct`s - Fields of enum variarnts - Fields of `union`s +- Enum variants Notes: @@ -298,13 +313,17 @@ struct NamedFields { #[derive(Foo)] struct UnnamedFields(#[ignore(Foo)] ()); -#[derive(Foo)] +#[derive(Foo, Bar)] enum Enum { + #[ignore(Bar)] NamedFields { #[ignore(Foo)] ignored: () }, - UnnamedFields(#[ignore(Foo)] ()) + #[ignore(Bar)] + UnnamedFields(#[ignore(Foo)] ()), + #[ignore(Bar)] + Unit, } #[derive(Foo)] @@ -616,7 +635,3 @@ struct Foo { ``` If desired, such a change will be possible to make in the future, but it is not part of this RFC because the first code block **already compiles** (although with a warning - `ignore only has an effect on functions`). - -## `#[ignore]` on enum variants - -There is no known reason why this would be useful, but it could be added in the future if needed From d7d7e9728e77a734487a655bdb80bd146c9154b8 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Tue, 7 Oct 2025 12:24:52 +0100 Subject: [PATCH 16/30] Fix spelling mistake --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 9142ab2d67d..7a211f6a7b4 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -477,7 +477,7 @@ However, this lead to [unacceptable breaking changes](https://github.com/rust-la > To give an update (at long last), the crater report did confirm my suspicion. We have 4 confirmed root regressions: [1], [2], [3], [4], [4.1], [4.2]. -Considering the breakage would compromise Rust's stabiligy guarantees, a different design is required: +Considering the breakage would compromise Rust's stability guarantees, a different design is required: > Given the crater results showing compatibility hazards, it sounds like this is going to need a design and lang RFC for an approach that avoids those. From 6a9de5952c783dd69cfe8a8e969feff02f0280e0 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Tue, 7 Oct 2025 12:42:36 +0100 Subject: [PATCH 17/30] Expand on existing valid forms of `#[ignore]` on fields --- text/3869-ignore-derive.md | 55 +++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 7a211f6a7b4..1e044a4ea6e 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -555,6 +555,47 @@ The lint was implemented in January 2019. See the [tracking issue](https://githu It has been almost 6 years since it became deny-by-default. It should be fine to promote it into a hard error as that's what this RFC would require for the feature-gate +## `#[ignore]` (without parentheses) on fields already compiles. Would this be a breaking change? + +You can currently apply `#[ignore]` to fields at the moment in 2 ways, which leads to a warn-by-default `unused_attributes` lint: + +- `#[ignore]` **without parentheses** +- `#[ignore = "some string"]` + +For example: + +```rust +struct Foo { + #[ignore] + ignored_1: String, + #[ignore = "for some reason"] + ignored_2: String, +} +``` + +The above gives warnings: + +``` +warning: `#[ignore]` only has an effect on functions + --> src/main.rs:2:5 + | +2 | #[ignore] + | ^^^^^^^^^ + | + = note: `#[warn(unused_attributes)]` on by default + +warning: `#[ignore]` only has an effect on functions + --> src/main.rs:4:5 + | +4 | #[ignore = "for some reason"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``` + +Both attributes do nothing, but are syntactically valid. Under this RFC, **they will continue to compile with warnings**, +probably with a changed error message to alert the user that they do absolutely nothing. + +Upgrading these warnings into a deny-by-default future incompatibility lint is discussed in the "Future Possibilities" section. + ## Why not choose a new name As seen with the `#[skip]` attribute attempt, it is likely to lead to breakages when we try to create a new built-in attribute for this. @@ -634,4 +675,16 @@ struct Foo { } ``` -If desired, such a change will be possible to make in the future, but it is not part of this RFC because the first code block **already compiles** (although with a warning - `ignore only has an effect on functions`). +If desired, such a change will be possible to make in the future, but it is not part of this RFC because +the first code block already compiles - it would be a breaking change. + +It's also not clear whether we'd want `#[ignore]` to work this way at all, so let's leave it up to a future +RFC to decide if giving `#[ignore]` (without parentheses) on fields meaning would be worth it. + +## Promote `#[ignore]` on fields (without parentheses) and `#[ignore = "reason"]` on fields into a deny-by-default lint + +Whilst it's not currently clear whether we want `#[ignore]` (without parentheses) on fields to actually do something, +we could upgrade the 2 currently useless forms of `#[ignore]` (without parentheses) on fields and `#[ignore = "reason"]` on fields into a +deny-by-default future incompatibility lint - just to be safe. + +This lint is not part of the RFC, and can be discussed separately. From a236ba6e1986b781441a05fd91fee6a082a0c424 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Tue, 7 Oct 2025 12:49:10 +0100 Subject: [PATCH 18/30] Expand on span section --- text/3869-ignore-derive.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 1e044a4ea6e..c7a9ee3f9a0 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -384,8 +384,13 @@ The `#[ignore]` attribute(s) for a particular derive macro `Foo` applied to the ## Spans Because input to the macro is different from what it is in the source code, we have to talk about -the `Span` that the derive macros see. Derives macros that don't support `#[ignore]` will want to report an error about it, -and they can do so by using span of the `ignore` identifier. +the `Span` that the derive macros see. Derive macros can use this span for various purposes. + +For example, since this RFC does not make an attempt to fit every possible use-case of ignoring fields, some derive macros +might require additional information about *how* to ignore the field. They can use this span to create an error and re-direct +users to their custom `#[foo(ignore(...))]` that might take arguments for example. + +### The span Given the following struct: From 99296f30d91b333ae45ee0dba6bf000c9cae36a1 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Tue, 7 Oct 2025 12:57:48 +0100 Subject: [PATCH 19/30] Require derive macros to support `ignore` attribute explicitly --- text/3869-ignore-derive.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index c7a9ee3f9a0..84ed8d006ba 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -381,6 +381,22 @@ The `#[ignore]` attribute(s) for a particular derive macro `Foo` applied to the - If the list of paths includes the derive macro `Foo` itself, then an `#[ignore]` attribute **without** any arguments is applied to the field - If the list of paths to `ignore` does not name the the derive macro, then the `ignore` attribute is fully removed. +## Declaring support + +Derive macros must explicitly declare that they support the `ignore` attribute: + +```rust +#[proc_macro_derive(Foo, attributes(foo, ignore))] +pub fn derive_foo(input: TokenStream) -> TokenStream { + // ... +} +``` + +If the derive macro doesn't support `ignore`, then any usage of `#[ignore]` on fields will yield an error. + +`#[ignore]` without parentheses, and `#[ignore = "some string"]` will compile and do nothing (with a warn-by-default or deny-by-default lint), +even if no support for the `ignore` attribute is declared by the derive macro. This is discussed further in the "Rationale and alternatives" section. + ## Spans Because input to the macro is different from what it is in the source code, we have to talk about From 53bb22c2bb02759cf80328125bd02c7d43da81cd Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Tue, 7 Oct 2025 13:04:15 +0100 Subject: [PATCH 20/30] Add some prior art in existing crates --- text/3869-ignore-derive.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 84ed8d006ba..c66e3523fab 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -661,7 +661,12 @@ There are numerous disadvantages to not having a single attribute in the standar # Prior art [prior-art]: #prior-art -There are no other languages with something like derive macros that directly have this feature built-in +Several crates in the Rust ecosystem currently support similar functionality. + +- [`#[serde(skip)]`](https://serde.rs/attr-skip-serializing.html) +- [`#[clap(skip)]`](https://docs.rs/clap/latest/clap/_derive/index.html#command-attributes) +- [`derive_more::Debug`](https://docs.rs/derive_more/latest/derive_more/derive.Debug.html) + is a more customizable version of the `std::Debug` derive and allows skipping individual fields with `#[debug(skip)]` # Unresolved questions [unresolved-questions]: #unresolved-questions From 2fe61b9cf18bbc70e1f2343e9c4de8432b59f433 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Tue, 7 Oct 2025 13:15:35 +0100 Subject: [PATCH 21/30] Mention that derive macros are required to opt-in to `ignore` in another place --- text/3869-ignore-derive.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index c66e3523fab..8f532f448f0 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -643,8 +643,11 @@ There are numerous disadvantages to not having a single attribute in the standar - Blocks standard library derives like `Hash`, `Ord`, `PartialOrd`, `Eq`, `PartialEq` from ignoring fields, because if adding the 1 `skip` attribute lead to so much breakage imagine if we added 5 new attributes -- People have to learn each crate's own way of skipping fields, if it exists at all. With `#[ignore]` on fields, the user can instead use the same syntax - for every derive macro. Derives that do not support `#[ignore]` attribute will be encouraged to emit a compile error to helpfully let the user know. +- People have to learn each crate's own way of skipping fields, if it exists at all. + With `#[ignore]` on fields, the user can instead use the same syntax for every derive macro. + + If they try adding a derive macro to the the list of paths in the `ignore` attribute, the user will be notified by the + compiler if the derive macro doesn't support the `ignore` attribute - as it is explicitly opt-in in `attributes(ignore)` - Standardizes syntax and makes reading Rust code more predictable. - Multiple `#[foo(ignore)]`, `#[bar(ignore)]` attributes each take up a separate line. A single `#[ignore(Foo, Bar)]` can instead nicely fit on a single line. - **Compile times.** If we have a separate crate re-implement the standard library `Hash` derives and others, then the compile times will be much worse since From 738aaa027ddbefd083bfafe50a790e2631ba3289 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Thu, 9 Oct 2025 01:57:54 +0100 Subject: [PATCH 22/30] Compiler is responsible for letting user know that a proc macro does not support `ignore` --- text/3869-ignore-derive.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 8f532f448f0..9bc47045dbd 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -189,8 +189,6 @@ Then it's up to the `std::hash::Hash` macro on how exactly it wants to use the ` - In the common case, it will exclude `foo` and `baz` from the generated `std::hash::Hash` impl - `std::hash::Hash` is allowed to ignore existence of the attribute. -- It could emit a `` compile_error!("`#[ignore]` is not supported for this derive") `` - or, in the future, a [diagnostic](https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html) ## Standard library macros that support `#[ignore]` From 66d404d7b4e2b5ef2a7d374bf4c9253c6cea489d Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 11:36:15 +0100 Subject: [PATCH 23/30] Do not implement `StructuralPartialEq` always --- text/3869-ignore-derive.md | 102 +++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 9bc47045dbd..b87bf5d2e63 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -483,6 +483,77 @@ This RFC additionally proposes to add 2 new deny-by-default lints: because it is logically incorrect for the implementations to differ. See the [documentation](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) for details. +## Standard library derives supporting the `ignore` attribute + +- `PartialEq` +- `PartialOrd` +- `Ord` +- `Debug` +- `Hash` + +### `#[derive(PartialEq)]` does not implement `StructuralPartialEq` if any fields are ignored + +By default, `#[derive(PartialEq)]` automatically implements [`StructuralPartialEq`](https://doc.rust-lang.org/std/marker/trait.StructuralPartialEq.html), +and the invariant automatically upheld is the following: + +> interpreting the value of the constant as a pattern is equivalent to calling PartialEq + +Essentially, given any type `Foo` implementing `PartialEq`, both A and B must be identical: + +```rust +#[derive(PartialEq)] +struct Foo { + foo: u32, + bar: bool +} +const FOO: Foo = Foo { foo: 10, bar: false }; + +// A +match foo { + FOO => print!("ok"), + _ => panic!() +} + +// B +match foo { + Foo { foo: 10, bar: false } => print!("ok"), + _ => panic!() +} +``` + +But if any field is `#[ignore(PartialEq)]`d, then the property would be violated: + +```rust +#[derive(PartialEq)] +struct Foo { + foo: u32, + #[ignore(PartialEq)] + bar: bool +} +const FOO: Foo = Foo { foo: 10, bar: false }; + +// Then this +match foo { + FOO => print!("not ok"), + _ => panic!() +} + +// Is actually this: +match foo { + Foo { foo: 10, bar /* doesn't matter */ } => print!("not ok"), + _ => panic!() +} + +// The above is NOT equivalent to this: + +match foo { + Foo { foo: 10, bar: false } => print!("ok"), + _ => panic!() +} +``` + +Hence any type deriving `PartialEq` with ignored fields will not implement `StructuralPartialEq` automatically + # Drawbacks [drawbacks]: #drawbacks @@ -715,3 +786,34 @@ we could upgrade the 2 currently useless forms of `#[ignore]` (without parenthes deny-by-default future incompatibility lint - just to be safe. This lint is not part of the RFC, and can be discussed separately. + +## `ignore` with arguments + +Some derives need to know how to ignore a field. It could be possible to allow passing arguments to paths in `ignore`: + +```rust +#[derive(MyTrait)] +struct S { + #[ignore(MyTrait())] // is any token stream + foo: Foo, + #[ignore(MyTrait)] + bar: Bar, + #[ignore(MyTrait = )] // is any expression + baz: Baz, +} +``` + +Which would give the following input to `MyTrait`: + +```rust +struct S { + #[ignore()] + foo: Foo, + #[ignore] + bar: Bar, + #[ignore = ] + baz: Baz, +} +``` + +efault field values would From 42d6a73162f4f92c06e4930a1aedb9c9aa28bc1e Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 11:39:37 +0100 Subject: [PATCH 24/30] formatting: Remove line break --- text/3869-ignore-derive.md | 1 - 1 file changed, 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index b87bf5d2e63..656c378ca6a 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -545,7 +545,6 @@ match foo { } // The above is NOT equivalent to this: - match foo { Foo { foo: 10, bar: false } => print!("ok"), _ => panic!() From f4c9ece76fd1ddd159816811dafd8a9d8d6de4a5 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 13:19:39 +0100 Subject: [PATCH 25/30] Fix copy-paste error --- text/3869-ignore-derive.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 656c378ca6a..a39289ef3dc 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -814,5 +814,3 @@ struct S { baz: Baz, } ``` - -efault field values would From 7959d18b2c4ad125200e6e4cfdf56a7b92ca28f6 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 13:21:02 +0100 Subject: [PATCH 26/30] Temporarily remove accidentally added section --- text/3869-ignore-derive.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index a39289ef3dc..03da261092a 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -786,31 +786,3 @@ deny-by-default future incompatibility lint - just to be safe. This lint is not part of the RFC, and can be discussed separately. -## `ignore` with arguments - -Some derives need to know how to ignore a field. It could be possible to allow passing arguments to paths in `ignore`: - -```rust -#[derive(MyTrait)] -struct S { - #[ignore(MyTrait())] // is any token stream - foo: Foo, - #[ignore(MyTrait)] - bar: Bar, - #[ignore(MyTrait = )] // is any expression - baz: Baz, -} -``` - -Which would give the following input to `MyTrait`: - -```rust -struct S { - #[ignore()] - foo: Foo, - #[ignore] - bar: Bar, - #[ignore = ] - baz: Baz, -} -``` From 9439d1fd9c1addd3d0f4e3b66c7406e0e66dce72 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 13:34:01 +0100 Subject: [PATCH 27/30] Future possibility: `ignore` with arguments --- text/3869-ignore-derive.md | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 03da261092a..477480734c8 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -786,3 +786,64 @@ deny-by-default future incompatibility lint - just to be safe. This lint is not part of the RFC, and can be discussed separately. +## `ignore` with arguments + +It's possible that derive macros might find it useful to know *how* they should be ignored. +We could allow passing arguments to paths in `ignore`: + +```rust +#[derive(MyTrait)] +struct S { + #[ignore(MyTrait())] // is any token stream + foo: Foo, + #[ignore(MyTrait)] + bar: Bar, + #[ignore(MyTrait = )] // is any expression + baz: Baz, +} +``` + +Which would give the following input to `MyTrait`: + +```rust +struct S { + #[ignore()] + foo: Foo, + #[ignore] + bar: Bar, + #[ignore = ] + baz: Baz, +} +``` + +This could be backward-compatible to add, so this is left for a future RFC to propose. + +However it's worth noting that there are several disadvantages with this: + +- It could encourage misuse of the `ignore` attribute, using the arguments as a sort of "default" value, + when it would be clearer to use [default field values](https://github.com/rust-lang/rust/issues/132162) +- Overloads meaning of the attribute, it is not necessarily always about "ignoring" any more, rather adding a condition to serializiation +- Makes it harder to read which derives are ignored. It's more reasonable to have a flat list of all ignored derives, + and if there is any metadata about ignoring, derives can use their own helper attributes for that. Prefer: + + ```rust + #[derive(Deserialize, Debug, Serialize, Parser)] + struct MyStruct { + #[ignore(Debug, Deserialize, Parser)] + #[serde(ignore_if(is_meaningless(name)))] + #[clap(ignore_if(is_meaningless(name)))] + name: String, + } + ``` + + Over stuffing all information in a single attribute: + + ```rust + #[derive(Deserialize, Debug, Serialize, Parser)] + struct MyStruct { + #[ignore(Deserialize(is_meaningless(name)), Debug, Parser(is_meaningless(name)))] + name: String, + } + ``` + +- None of the derives in the standard library would use this feature, and few crates would find it useful where default field values don't solve the issue. We would be supporting a mostly niche use-case From 761ab5236e728c9e675ae981e03546102e525ab8 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 13:38:42 +0100 Subject: [PATCH 28/30] Clarify when `PartialEq` is ignored --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 477480734c8..45359cd113b 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -551,7 +551,7 @@ match foo { } ``` -Hence any type deriving `PartialEq` with ignored fields will not implement `StructuralPartialEq` automatically +Hence any type deriving `PartialEq` with fields that are marked `#[ignore(PartialEq)]` will not implement `StructuralPartialEq` automatically # Drawbacks [drawbacks]: #drawbacks From 9c45c6fe0186998ca845e98dcde6b59bff7c3fa0 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 13:45:34 +0100 Subject: [PATCH 29/30] minor: Wording of title --- text/3869-ignore-derive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 45359cd113b..49b8e0fece2 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -685,7 +685,7 @@ probably with a changed error message to alert the user that they do absolutely Upgrading these warnings into a deny-by-default future incompatibility lint is discussed in the "Future Possibilities" section. -## Why not choose a new name +## Why not another name? As seen with the `#[skip]` attribute attempt, it is likely to lead to breakages when we try to create a new built-in attribute for this. The `ignore` keyword is very convenient for us, because it lets us implement this feature using an understandable keyword From ea62fb3a7d1878c13cb23bf659809b241e011013 Mon Sep 17 00:00:00 2001 From: Nik Revenco Date: Fri, 10 Oct 2025 13:50:21 +0100 Subject: [PATCH 30/30] Mention benefits for tooling (rustfmt, rust-analyzer) --- text/3869-ignore-derive.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/text/3869-ignore-derive.md b/text/3869-ignore-derive.md index 49b8e0fece2..a027b81fa4a 100644 --- a/text/3869-ignore-derive.md +++ b/text/3869-ignore-derive.md @@ -716,12 +716,19 @@ There are numerous disadvantages to not having a single attribute in the standar If they try adding a derive macro to the the list of paths in the `ignore` attribute, the user will be notified by the compiler if the derive macro doesn't support the `ignore` attribute - as it is explicitly opt-in in `attributes(ignore)` -- Standardizes syntax and makes reading Rust code more predictable. - Multiple `#[foo(ignore)]`, `#[bar(ignore)]` attributes each take up a separate line. A single `#[ignore(Foo, Bar)]` can instead nicely fit on a single line. - **Compile times.** If we have a separate crate re-implement the standard library `Hash` derives and others, then the compile times will be much worse since they'll require additional dependencies like `syn` and miss out on the optimizations that can be gained with a derive macro that is included in the standard library +Other benefits of `ignore` attribute include: + +- Standardizes syntax and makes reading Rust code more predictable. +- Allows language servers like rust-analyzer to provide suggestions for the list of paths an `ignore` attribute would accept, + making this functionality more discoverable +- Similar to how multiple `#[derive]`s are merged into a single `#[derive]` by rustfmt, multiple `#[ignore]` + attributes could be merged into a single `#[ignore]` attribute + ## What is the impact of not doing this? - Less standardization in the ecosystem. Each crate can have its own way to do things.