-
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: #[derive_no_bound(..)]
and #[derive_field_bound(..)]
#2353
Conversation
+ Replace bounds on impl of `Clone` and `PartialEq` with `T: Sync` | ||
|
||
```rust | ||
#[derive_bound(Clone, PartialEq, T: Sync)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attributes currently doesn't support this syntax (T: Sync
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, this is what was meant by:
Changing this would require a language change.
I'll clarify =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be quite a large language change 🙂 And I wonder if this will leads to things like
#[derive_bound(T: PartialEq<#[thing] fn(i32)>)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand I think steps to at least provide a sensible subset of things such as bounds, expressions, types in the attribute grammar is good as people already do this kinds of things in serde, derivative, (my derive crate for proptest also does this, but it is still WIP)... It is just done in string quotes which is somewhat hacky :(
That is, afaik, people can already write:
#[serde(bound = "T: PartialEq<#[thing] fn(i32)>")]
I think we should provide custom derive macro authors and users ways to do these things in a less hacky way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to say that I clarified this =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't attributes allow arbitrary token trees now? This should make this acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@clarcharr I don't think so, but I'm not sure - I'd love to be proven wrong =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attributes do allow arbitrary token trees between their brackets now.
…[derive_bound(..)]"
Did anyone think about automatically detecting whether the bounds on the type parameters are required? I think this should at least be listed as a theoretical alternative. The downside would be that it's less visible (especially if the fields are private) and easier to accidentally break APIs. The proposed syntax seems to be the "safe" option; but it's also quite heavy to use imho. Another alternative would be allowing things like: #[derive]
impl<T> Clone for MyArc<T>; Or in more crazy scenarios: #[derive]
impl<T: UnrelatedTrait> Clone for MyArc<T>; This probably requires more lines of code, but is imho better readable, and allows more powerful constructs. |
That cannot be done, that's why this is not listed as an alternative, deriving is a syntactical thing and syntactical things don't have any type knowledge.
What is
Not being able to know the bounds just from reading the code isn't more readable IMO. |
Since all those derives (clone, copy, eq, cmp, ...) rely entirely on forwarding the implementation in some way to the struct's fields, wouldn't it always work if we never add bounds and instead always bind on field types? So basically the equivalent of |
No, because of privacy. You can't have a where clause involving a private type in a public one. And changing that now would be a breaking change anyway. |
And there I thought that part would be obvious... #[derive]
impl<T: Clone> Clone for MyArc<T>; would be the equivalent to what #[derive(Clone)]
struct MyArc<T>(...); is today: it should automatically derive the trait implementation. The derive attribute is empty, because the trait to be derived is already given.
Hm right; this means my idea only works if the compiler manages to find the type definition in that stage. Maybe restricting this to only be allowed in the same file after the type definition would make this work?
This second idea is unrelated to my first about automatically finding the bounds; this one requires writing the bounds just like in a normal implementation, but allows deriving the implementation even if the bounds are more complex. You're trying to solve the more complex cases with Sorry about the confusion. |
Things need to be exhaustively described in a RFC, we don't have room for confusion. Deriving the trait implementation from an I think this idea should be the way to go if someone needs to go beyond what's provided by the attributes in this RFC, but it doesn't ultimately supersede it.
Even in the same file, a derive macro doesn't have access to the definitions of types used in the item it's deriving things for. |
The privacy issue is fixable, in theory it's already fixed by #2145, it just needs implementation. |
I disagree. I think the proposed syntax is real ugly; I don't look forward to using it, although the feature itself is quite important imho. So if there are better ways doing it, those are imho related to this RFC.
Yes. I'm fine with that compared to the syntax currently proposed.
Doesn't look like an important argument.
But either
Yes, and that's why I like it. Because it's way easier to see what bounds are required for a certain trait compared to reading a big mess of attributes.
I disagree again; I'd rather not see the attributes getting implemented in the first place if a better solution can be found.
I'm not familiar with the implementation here, and not sure where this is in the range of "theoretically possible, but not gonna happen", "would be a lot of work" or "trivial to change". But it's certainly not impossible to implement. I admit I have no idea how to combine my idea with proc_macros (although I'm pretty sure I wouldn't want to parse the proposed attributes either). |
I look forward to using it because I actually need it or my code doesn't compile. As for the syntax being ugly, the syntax in that RFC lets me have less code duplication than your suggestion, so I don't really see what you want me to tell you.
And I'm not because I will have around 60 types needing this.
Well, that's your opinion. I will have around 60 types needing this.
It won't work, but my types are public, and I will not have to write them once in the type and once in each derived impl, which is better than your proposal.
I have types that derive more than 10 traits. I have around 60 types like that.
Then show me a solution that doesn't lead to inordinate amounts of code duplication. Edit: I forgot a verb. |
My feeling is that in Rust it is preferred to have more code over having compact and unreadable code. Maybe you could use macros to organize your types in a more compact way. It seems you're also motivated by some time constraints, but it is not obvious why those are relevant for the Rust community. All in all I'd still like to see the feature itself; so if the requirement is that customizing the bounds for derive needs to be done in attributes in the type definition, you're proposal seems overall fine to me. |
I'm not really motivated by some time constraint, given my project is blocked on at least 2 other unstable features. I'm motivated by not duplicating code. Your idea leads to duplicating code immensely. There are even cases where I'm already using macros extensively, that's why I can derive more than 10 traits on them. There is not much you can do when your work is to encode more than 300 different CSS properties with a huge variety of types and intricacies. Finally, I still don't see what's supposed to be unreadable about the attributes suggested in this RFC. |
#[derive]
impl<T: UnrelatedTrait> MyTrait for MyType<T>; Of the top of my head that is an interesting extension, but I agree with @nox that it is more verbose, less local and should be used when deriving still can't figure it out, but for specific problem this RFC attempts to solve, I think it is not the better solution. The problem with
By all means, if you have some other attribute names in mind or a method that is equally terse as the one proposed in the RFC, I'm all ears. Tho, I don't understand why you think it is ugly... It builds upon what we really have, attributes, so I think it is consistent.
I'm not sure it is morally right to fix it... seems somewhat strange to me that you should be able to refer to private types in public types's impl where clauses..? |
I like this a lot but I wish there were a way to make the names less... unwieldy. |
They are just a bit lengthy, but does that really matter? |
@clarcharr
We originally went without |
Oh this, with phantom typing. DO WANT! Have you considered to talk about type roles? |
Type roles are way more constrained than what this RFC allows AFAICT.
|
I think that |
The |
Ah thanks, I did not think this works on the item level attributes alongside the macro's own attribute, too. |
See the RFC:
|
Like mentioned in #2811 (comment) I think this is mostly a shortcoming of how #[derive(Clone)]
struct X<T> {
x: PhantomData<T>
} the generated bound is |
The problem with "just making #[derive(Clone)]
pub struct PubType<T> {
inner: PrivType<T>,
}
#[derive(Clone)]
pub(self) struct PrivType<T>(T); If the generated The "most correct" "smart" version would recursively expand these bounds until reaching public types to bound, but this starts to be very magic and hard to control what implementation details are publicized (and thus what changes are breaking). Not to mention it then adds ordering constraints on derive expansion and requires much more type information than current macros which are pure over their input. |
|
Yes that makes sense. I didn't think about private types. I still think for some simple cases the derive could be made smarter (e.g. I also noticed that in the Prior arts section of Haskell it says:
while Haskell doesn't have attributes like Rust it does have the data Foo a = Bar a | Baz String
deriving instance Eq a => Eq (Foo a)
Source: https://downloads.haskell.org/~ghc/7.8.4/docs/html/users_guide/deriving.html#stand-alone-deriving This is basically what this RFC is trying to accomplish, right? Although probably a bit more verbose. Syntax like that would also allow to derive a trait for a more specific instance. E.g. deriving instance Eq a => Eq (Foo [a]) This would maybe translate to rust syntax that looks like this: #[derive]
impl<T> Clone for Struct1<T>;
#[derive]
impl<T: Clone, S> Clone for Struct2<T, S>;
#[derive]
impl<T, S> Clone for Struct2<T, S> where T: Clone;
#[derive]
impl Clone for Struct1<String> where T: Clone; which is probably a lot more repetitive than what this RFC suggests but IMHO it is also a lot more readable and basically mirrors what you would find in the generated documentation. Edit: Also like the Haskell docs mention this would allow the proc macros responsible for deriving the trait work like they do now (generate the boilerplate) and the compiler would replace the generated bounds with the bounds given by the user. |
This has the same expressive strengths and weaknesses as #2811 in its current shape (as of eaaa257), but will require a change in Rust syntax. I like the idea of there being a greppable |
Should the compiler treat specially the situation when Edit: Re-reading the rule above, it does seem to call for special treatment that does not automatically fall out of the current behavior over helper attributes. |
This has been suggested before and requires repeating the field types if that's what you want to put trait bounds on. |
This comment has been minimized.
This comment has been minimized.
@rfcbot fcp close We discussed this RFC in our "Backlog Bonanza". The consensus was that we will close it, even though we are interested in solving this general problem. The solution here doesn't seem to be 100% on target. The right next step to carry this work forward would be to create a lang team project proposal, because we would want to charter an effort to explore and write-up the design space. Thanks all. |
Team member @nikomatsakis has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@rfcbot reviewed (I checked the names of folks who were present in the meeting.) |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to close, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC is now closed. |
@nikomatsakis Is there somewhere that interested parties can watch for the lang team project proposal? |
https://github.com/rust-lang/lang-team would be the place |
🖼️ Rendered
📝 Summary
This RFC gives users a way to control trait bounds on derived implementations by allowing them to omit default bounds on type parameters or add bounds for field types. This is achieved with the two attributes
#[derive_no_bound(Trait)]
and#[derive_field_bound(Trait)]
.The semantics of
#[derive_no_bound(Trait)]
for a type parameterP
are:The type parameter
P
does not need to satisfyTrait
for any field referencing it to beTrait
The semantics of
#[derive_field_bound(Trait)]
on a field are that the type of the field is added to thewhere
-clause of the referencedTrait
as:FieldType: Trait
.💖 Thanks
Thanks to @nox for collaborating on this RFC with me as well as @kennytm for providing some great input.