-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Customizing #[derive(Debug)]
#334
Comments
Copying a suggestion from the internals thread:
|
I went with |
Procedurally, is an ACP sufficient? This feels like a relatively large change. |
Note that the choice here has certain implications for the ecosystem. Going with a |
I've added a link to the Pre-RFC I made on IRLO; Josh suggested filing an ACP so it could be nominated for discussion. |
@jhpratt I think an ACP is sufficient; this seems like a small change, which would be a relatively small PR, and the primary challenge is deciding "should we do this", which is what we have ACPs for. |
Copying from IRLO:
TBH, I prefer this phrasing. It makes the proposal "add a common Then But that direction would implicitly be a "also, ecosystem, maybe you should support skip like this too", at which point an RFC becomes more useful. |
I like the idea of a common I am not sure about A similar concern around skipping. That is easily resolved by still showing the name of the field. |
We discussed this in today's @rust-lang/libs-api meeting. We'd like to go ahead and approve We also felt that there should be some syntax to skip a field for all field-wise derives, without having to repeat the whole list. Something, probably rustc, needs to lint against un-handled arguments for And it would probably also make sense to have a lint for the cases scottmcm mentioned: skipping a field for a subset of derives that seems unlikely to make sense, such as deriving more than one of |
How do you know it's unhandled if a 3rd party derive might use it? Or is the assertion that |
@cuviper If Making it a lint and not an error also makes it easy enough to disable. |
Technically speaking this is a breaking change though isn't it? If a pre-existing crate exposes a derive macro with a helper attribute I guess the same could've been said about Edit: Ah, furthermore, the feature gate error alone can also break existing packages. |
I'm currently implementing the unsupported skip argument as a lint so it can be disabled if needed (although right now there seem to be issues with that). |
Revisiting this I think we should be more careful with introducing more and more unnamespaced helper attributes. If we continue this trend ( As the last comment from the linked IRLO thread mentions, ideally we would have better language support for this: Qualified helper attributes or better yet fully hygienic helper attributes (*). This comes close to a pre-RFC and blocking (*) If an (attr or derive) proc macro declares a helper attribute Maybe we should nominate this for T-lang discussion. I still plan on reviewing the implementation of |
cc @petrochenkov (you might be interested in this topic) |
Or that |
Curious question: how should third party libraries integrate with that |
I think the field that is Ideally,
but it may be too late to change (1) now. Proof of conceptextern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(First, attributes(first_a, first_b, common))]
pub fn derive_first(item: TokenStream) -> TokenStream {
println!("derive(First): {item}");
TokenStream::new()
}
#[proc_macro_derive(Second, attributes(second_a, second_b, common))]
pub fn derive_second(item: TokenStream) -> TokenStream {
println!("derive(Second): {item}");
TokenStream::new()
} #[derive(Default, First, Second)]
pub struct F {
/// documentation
#[rustfmt::skip]
#[first_a]
pub a1: u8,
#[allow(unknown_lints)]
#[second_a]
pub a2: u8,
#[cfg_attr(any(), first_a)]
#[cfg_attr(all(), second_a)]
pub a3: u8,
#[first_b]
#[second_b]
pub b: u8,
#[common]
pub c: u8,
#[cfg(any())]
pub d: u8,
#[skip(First)]
#[first_a]
pub skip_first: u8,
#[skip(Second)]
#[first_a]
pub skip_second: u8,
#[skip]
#[first_a]
pub skip_all: u8,
} Current output is something like:
Ideal output:
This is also the problem that for trait that is going to construct an object like #[derive(Default)]
struct G {
#[skip(Default)]
pub a: u8,
}
// This will generate:
impl Default for G {
fn default() -> Self {
Self {
} // which is a compilation error because the constructor must fill in all fields including `a`!
}
} |
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]. As such and for the other reasons I gave above, I deem the feature as currently designed unacceptable. I'm willing to work with the author @clubby789 on alternative designs if they agree to it like ones based on "hygienic" helper attributes. In any case, I'm going to submit a T-lang meeting proposal at the end of July — latest — hopefully containing an actionable solution sketch. |
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. Happy to work with anyone looking to write one. @fmease? |
@joshtriplett Happy to write one this week and open a meeting proposal over at rust-lang/lang-team. |
Proposal
Problem statement
The current behaviour of the
#[derive(Debug)]
macro can be overly broad and not work for some use cases. Users with types that do not fit the derive are forced to either write verbose custom implementations (which they must remember to update when the type is updated), or a 3rd-party derive. These approaches both mean missing out on upstream optimizations to the macro.Motivating examples or use cases
Solution sketch
Introducing a new
#[debug(..)]
attribute which can be applied to fields or types to customize the behavior of the derive.skip
#[debug(skip)]
on a field will cause it to not be emitted, and not require the type of that field to implementDebug
.Ideally,
should not place a
: Debug
bound onT
.transparent
Similarly to
repr(transparent)
,#[debug(transparent)]
may be placed on any type containing a single field to use the debug implementation of that field directly. The macro should expand to something likeOther ideas
Other attributes that I am less commited to, but could be of use:
#[debug(type)]
- expands to thestd::any::type_name
of the field - useful for function pointers to see the source location#[debug(format_with = "path")]
- a more general version oftype
, allowing a custom function to be used to format the field#[debug(rename = "identifier")]
- changes the name of a field in the debug outputAlternatives
The main alternative is using a 3rd-party crate such as
derivative
. The reasoning against this is explained in the problem statement.Links and related work
Derivative - One of the top 350 most downloaded crates on crates.io.
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution:
Internals thread
The text was updated successfully, but these errors were encountered: