Skip to content

Conversation

nik-rev
Copy link

@nik-rev nik-rev commented Oct 6, 2025

This RFC proposes that the #[ignore] attribute can now be applied to fields.
Its purpose is to tell derive macros to ignore the field when generating code.

#[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:

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<H: std::hash::Hasher>(&self, state: &mut H) -> () {
        std::hash::Hash::hash(&self.id, state)
    }
}

Rendered

@fmease
Copy link
Member

fmease commented Oct 6, 2025

This can and should probably be an ACP, not an RFC.

CC rust-lang/libs-team#334 from >1 year ago which proposed something very similar for Debug. Its implementation had to be blocked because adding new helper attributes to built-in derive macros in core/std is a breaking change (due to lack of "hygienic" or namespaced helper attrs). See link for details.

Similarly, starting to interpret your proposed #[ignore] as a helper attribute is not backward compatible without extending the language (via separate proposals) as alluded to above (not only due to the feature gate error but also due to any validation that's performed).

(We got away with the addition of #[default] for Default either because we were lucky or because it was deemed acceptable at the time (I'd have to dig into the history to find out the specifics) but in general that's not principled and goes against Rust's stability guarantees.)

@nik-rev
Copy link
Author

nik-rev commented Oct 6, 2025

This can and should probably be an ACP, not an RFC.

CC rust-lang/libs-team#334 from >1 year ago which proposed something very similar for Debug. Its implementation had to be blocked because adding new helper attributes to built-in derive macros in core/std is a breaking change (due to lack of hygienic or namespaced helper attrs). See link for details.

Published it as an RFC that is what was suggested in this comment

Validation: The following doesn't compile due to a deny-by-default future incompatibility lint which is going to be promoted to a hard error as part of this RFC:

use derive as ignore;

struct Foo {
    // syntax that will gain meaning because of this RFC
    #[ignore()]
    hello: ()
}

This does not compile because of ambiguity. Whereas if ignore wasn't already built-in, then it would which would be breaking

use derive as ignore;

struct Foo {
    #[ignore]
    hello: ()
}

So then giving this meaning should be fine:

use derive as ignore;

struct Foo {
    #[ignore(Foo, Bar)]
    hello: ()
}
struct Foo {
    #[ignore(Foo, Bar)]
    hello: ()
}

Because it would not have compiled (without turning off the deny-by-default lint which says that we will promote it into a hard error in the future)

This currently does compile, with a warn-by-default lint. And in the RFC I'll clarify that this will continue to compile, and its meaning will not be changed.

struct Foo {
    #[ignore]
    hello: ()
}

I'm not super familiar with terminology, but this isn't a new helper attribute - it's the same attribute #[ignore] that is re-purposed for this proposal.

Summary: If any other name was chosen, then it could be potentially breaking. But because ignore is already a built-in attribute, I think it won't cause breakage aside from upgrading the deny-by-default future incompatibility lint from 6 years ago to an error

If there are any possible breaking changes that I've missed, I would appreciate an example

@fmease
Copy link
Member

fmease commented Oct 6, 2025

Ah, I completely forgot that #[ignore] is of course already a built-in attribute. In that case, it looks like it's indeed backward compatible, thanks for the correction :)

Still, it would "need to become" a helper attribute but that would clash with the built-in attribute, or rather the built-in attribute #[ignore] (used for ignoring tests) would take precedence under the current rules.

However, I'm not an expert in those resolution rules, so maybe it's a non-issue, so I'm happy to be corrected by someone more knowledgeable. I'm wondering if we can properly discern helper and built-in attribute #[ignore] inside ADTs under #[derive]s without some sort of hacks (e.g., not resolving #[ignore] to the built-in attr if it's on a field or variant; there's no precedent for that AFAIK).

@fmease
Copy link
Member

fmease commented Oct 6, 2025

CC https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/namespacing.20macro.20attrs.20to.20reduce.20conflicts.20with.20new.20adds/ (from Jul–Sep '25) (don't quite remember if this is only tangentially related or indeed fully)

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,
Copy link
Member

@programmerjake programmerjake Oct 6, 2025

Choose a reason for hiding this comment

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

I think we should let #[ignore(MyDerive)] take arguments since some derives need to know how to produce an ignored field,
e.g. clap::Parser uses #[arg(skip = <expr>) to fill in that field with <expr> when parsing your struct from the command line.

I suggest:

#[derive(MyTrait)]
struct S {
    #[ignore(MyTrait(<args>))] // <args> is any token stream
    foo: Foo,
    #[ignore(MyTrait)]
    bar: Bar,
    #[ignore(MyTrait = <arg>)] // <arg> is any expression
    baz: Baz,
}

which gives the following input to MyTrait's derive:

struct S {
    #[ignore(<args>)]
    foo: Foo,
    #[ignore]
    bar: Bar,
    #[ignore = <arg>]
    baz: Baz,
}

Choose a reason for hiding this comment

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

Not saying that I've thought of all possibilities, but that feels like a case where default field values would be a better solution to arguments.

Copy link
Member

Choose a reason for hiding this comment

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

arguments also let you write stuff like:

#[derive(Serialize)]
struct MyStruct {
    #[ignore(Serialize(if self.name_is_meaningless()))]
    name: String,
}

Choose a reason for hiding this comment

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

Sure, but that's not really "ignoring" the field; it's adding a condition to serialisation. I would rather not overload the meaning of the attribute too much.

In the case of constructing the object, you're right that you can't ignore the field, and thus need something to put in its place. Default values solve that. Conditional serialization isn't really ignoring; it's just changing what the trait does to the field, if that makes sense.

@fmease fmease added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Oct 7, 2025
Copy link
Contributor

@madsmtm madsmtm left a comment

Choose a reason for hiding this comment

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

Really elegant solution with transforming #[ignore(Xyz)] -> #[ignore]!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants