-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Generic parameters in derive #2811
base: master
Are you sure you want to change the base?
Conversation
So with this proposal, |
@nox The derive macro for |
The attribute input is parsed as a DelimTokenTree; make the proposed syntax with where clauses conform to that.
There may be bounds that apply to types other than the generic parameters, so it makes sense to approve the where clause in this proposal. This calls for a struct to organize all the generic-carrying inputs to `proc_macro_derive_with_generics`, which was mentioned previously as a question. Name it DeriveGenerics. The extended derive syntax is now fully specified. It's now clear to me that adorning each comma-separated item with its own generic parameters is possible, but the where clause either needs ugly enclosing delimiters or can only come last. As it relates to some parameters, make it so that the where clause can only be appended to a single item.
Wait, so you write down the names of the type params for which you don't want a trait bound? That seems counter-intuitive. Let's say you have |
And how do you handle trait bounds on field types without repeating those field types? |
TL;DR: I think this is a shortcoming of the derive implementation for If I understand correctly the Problem you're trying to solve is that for struct X<T> {
x: PhantomData<T>
}
impl<T> Clone for X
where
T: Clone
{
fn clone(&self) -> Self {
Self { x: x.clone() }
}
} where it should actually generate: impl<T> Clone for X
where
PhantomData<T>: Clone // this is implemented for every T
{
fn clone(&self) -> Self {
Self { x: x.clone() }
}
} I think this is more a problem of how the derive for Clone (and others) is implemented. It should generate the bound from the field types ( If you were to actually ignore the type parameter when deriving It might be useful to have uniform way to give arguments to derives but I think the information you want to provide to the derive implementation is so varied that the way it's currently done is good (custom attributes). |
That won't work; you need to list all of the parameters of the impl item: #[derive(<T, U: Clone> Clone)]
struct Foo<T, U> {
// ...
} The idea is that the derive macro can paste these parameters verbatim after the
The answer is that you apply bounds to non-parameter types only if you have to (in the |
I think you are conflating the field type with the generic bound that needs to be satisfied so that |
Add another magic helper attribute to the state machine future, to specify failure handling that needs to happen after the first step.
There are many types in Stylo, Servo's styling system, where we can't just have general |
I'm interested to look at the examples and the design considerations that led to a proliferation of non-uniform impls for derivable traits. |
We have generic types that implement a given trait (let's say |
That sounds like a major inconvenience for developing any kind of regular trait coverage. But yes, for this kind of design the bounds would need to be verbose. |
Things are this way because that's the most natural way for them to be, and all of our |
I think this is the only correct and most elegant solution to this problem, not having to enter bounds in the derive itself. This solution was actually implemented in my I think it might actually be possible to implement this without breaking backwards compatibility for the current derives, because it should only fix derives that are generating invalid code now (I might be missing some cases though, so don't take my word on this). (As you can see in the issue, the Display derive actually needed other bounds to be specified as well. But that was because the derive can be configured to call any method on the fields, which is not the case for the default derives from |
@JelteF If the field types are private and the type for which we derive stuff is public, last I checked it will not pass privacy checks. |
@nox changing the outer type to
Maybe this should be a reason to reconsider phasing this behaviour out (or modifying it so that this type of case is allowed). It seems to me that this should be the way you would want to implement an implementation like this. As far as I can tell there's no reason for this type of trait implementation to be disallowed. If we wanted |
Actually, in the tracking issue for that lint there is the following comment that basically describes this exact issue: |
Private-in-public is only part of why leaking field types in bounds is problematic. Even if the type of a private field is public (as e.g. |
Do you mean that someone can implement Clone for PhantomData. Without implementing clone for TheirType? |
I see only six instances in this list where non-parameter bounds were needed. Let's pick a simple one: impl<H, V, NonNegativeLengthPercentage> ToCss for Circle<H, V, NonNegativeLengthPercentage> where
GenericPosition<H, V>: ToCss,
NonNegativeLengthPercentage: ToCss + PartialEq, Why can't there be a generic |
@JelteF What I mean is that changing this: use some_public_api::Bar;
pub struct Foo<T> {
inner: Bar<T>
}
impl<T> Clone for Foo<T> where Bar<T>: Clone {
// ...
} to this: use some_public_api::Baz;
pub struct Foo<T> {
inner: Baz<T>
}
impl<T> Clone for Foo<T> where Baz<T>: Clone {
// ...
} is a breaking change for users of |
As further analyzed in servo/servo#24733, of these only two have a |
A legitimate reason for having bounds on public field types might be maintainability: like in the Servo example, you are certain, due to the design constraints on the subject functionality, that a certain struct has to have a field of a certain public type, so it makes sense to define the bounds on the struct's impl through those of the field. This looks like a design decision to me, and as such, it deserves some more explicit syntax. |
My what I meant with my question was: in what case does it actually break
when these bounds change? For instance changing from Arc to Box doesn't,
because they both implement Clone for all T. And in this breaking case,
what Clone implementation would you want to have?
I'm asking, because in the end this where statement is the most minimal
automatic bound you can give. It sounds like on one hand you want the
minimal viable bounds, but on the other you hand you don't. (the first case
is explained in the RFC, but the second not really)
…On Thu, 14 Nov 2019, 01:34 Mikhail Zabaluev, ***@***.***> wrote:
@JelteF <https://github.com/JelteF> Clone is implemented
<https://doc.rust-lang.org/std/marker/struct.PhantomData.html#impl-Clone>
for PhantomData regardless of the parameter type.
What I mean is that changing this:
use some_public_api::Bar;
pub struct Foo<T> {
inner: Bar<T>
}
impl<T> Clone for Foo<T> where Bar<T>: Clone {
// ...
}
to this:
use some_public_api::Baz;
pub struct Foo<T> {
inner: Baz<T>
}
impl<T> Clone for Foo<T> where Baz<T>: Clone {
// ...
}
is a breaking change for users of Foo, and the bounds like that are best
avoided because they expose an implementation detail of Foo.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2811?email_source=notifications&email_token=AAI3YJU4IA6RSDASECBRUVLQTSMJHA5CNFSM4JLO4P72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEAFFIA#issuecomment-553669280>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAI3YJSDR76YECC44H65723QTSMJHANCNFSM4JLO4P7Q>
.
|
It should be noted that any custom helper attributes to further reduce boilerplate, or even a systematic solution like #2353, are not incompatible with this proposal. It only makes them needed in fewer cases. |
Consider a type in another crate that embeds
I normally want minimal necessary bounds that don't expose the internal details of a type. |
Just to be clear, you mean something like this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f13f8658e57d192d3c4e74a4514d3c61 My question is, what bounds on the Clone implementation would you want in this case then? Simply |
I'd prefer a less contrived example because there is no good reason for |
This is just your example written from your comment written out as far as I can tell. So if you think it's too contrived (I agree it is), that's because your original example already was. I cannot think of a non contrived example, that's why I was asking you for it. |
My example did not have this irregular trait impl: impl Clone for Bar<MyType> {
fn clone(&self) -> Self {
Self {
data: MyType{}
}
}
} Sure this forces a bound on |
I'm just trying to understand your example, but afaict you just effectively
said that it's incomplete. So I don't know the answer to your question.
Seems like you should think of that given it's your example
…On Thu, 14 Nov 2019, 09:45 Mikhail Zabaluev, ***@***.***> wrote:
This is just your example written from your comment written out as far as
I can tell. So if you think it's too contrived (I agree it is), that's
because your original example already was.
My example did not have this irregular trait impl:
impl Clone for Bar<MyType> {
fn clone(&self) -> Self {
Self {
data: MyType{}
}
}
}
Sure this forces a bound on Bar<T>: Clone to be able to cover MyType, but
is there a non-contrived reason why MyType should not implement Clone
itself and therefore be covered by the derived impl<T: Clone> Clone for
Bar<T>?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2811?email_source=notifications&email_token=AAI3YJXUN6FF4FNVVWEV37LQTUF2RA5CNFSM4JLO4P72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEBBR7Y#issuecomment-553785599>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAI3YJQGHZAFXRH3XPJLW3DQTUF2RANCNFSM4JLO4P7Q>
.
|
@JelteF As far as I understand, your addition is meant to justify a bound on a private field type. Unfortunately, it does not go far enough in showing what real purpose would this design be useful for, because you can just derive |
Add ability to pass generic parameters of the impl to the derive macros, greatly increasing the flexibility of the
derive
attribute.Rendered