From a2c04c9cf33c6ac3702c99a71648838447374c4f Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Feb 2018 01:29:48 +0100 Subject: [PATCH 1/9] rfc, derive_bound_control: initial version --- text/0000-derive-bound-control.md | 771 ++++++++++++++++++++++++++++++ 1 file changed, 771 insertions(+) create mode 100644 text/0000-derive-bound-control.md diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md new file mode 100644 index 00000000000..e04d9e498b2 --- /dev/null +++ b/text/0000-derive-bound-control.md @@ -0,0 +1,771 @@ +- Feature Name: derive_bound_control +- Start Date: 2017-02-26 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #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 `#[no_bound(Trait)]` and `#[field_bound(Trait)]`. + +The semantics of `#[no_bound(Trait)]` for a type parameter `P` are: +> The type parameter `P` does not need to satisfy `Trait` for any field +> referencing it to be `Trait` + +The semantics of `#[field_bound(Trait)]` on a field are that the type +of the field is added to the `where`-clause of the referenced `Trait` +as: `FieldType: Trait`. + +# Motivation +[motivation]: #motivation + +The deriving mechanism of Rust allows the author to prototype faster and reduce +pain by significantly reducing boilerplate in many cases. Deriving also allows +readers of code to easily see when a bunch of simple delegating `impl`s are +defined instead of reading such boilerplate as manual `impl`s. + +Unfortunately, there are many cases where deriving fails to produce the code +indented by manual implementations. Either the `impl`s produced are too +restrictive by imposing bounds that shouldn't be there, which is solved by +`#[no_bound(..)]`, or not enough bounds are imposed. When the latter is the +case, deriving may fail entirely. This is solved by `#[bound(..)]`. + +The crate `serde` provides the attribute `#[serde(bound = "T: MyTrait")]`. +This can be used solve the same issues as in this RFC. This RFC proposes a +common mechanism to be used for all derivable traits in the standard library, +as well as in custom derive macros. By doing so, a common language is given +to users who can now use this method regardless of what trait is being derived +in all of the ecosystem. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## Removing bounds in derive with `#[no_bound]` + +Let's consider a simple new-type around an `Arc`: + +```rust +#[derive(Clone)] +struct MyArc<#[no_bound] T>(Arc); +``` + +or, to apply `#[no_bound]` to all type parameters, which is in this case +equivalent: + +```rust +#[derive(Clone)] +#[no_bound] +struct MyArc(Arc); +``` + +The resulting `impl` will be of the form: + +```rust +// There is no bound T: Clone! +impl Clone for MyArc { /* .. */ } +``` + +We see that `#[no_bound]` on `T` is an instruction to the derive macro for +`Clone` that it should not add `T: Clone`. This applies to any trait being +derived and not just `Clone`. This works since `Arc: Clone` holds regardless +of whether `T: Clone` or not. + +But what if you want to differentiate between the deriving behavior of various +traits? Let's derive another trait, `PartialEq`, but still use `#[no_bound(..)]`: + +```rust +#[derive(Clone, PartialEq)] +struct MyArc<#[no_bound(Clone)] T>(Arc); +``` + +We can equivalently write: + +```rust +#[derive(Clone, PartialEq)] +#[no_bound(Clone)] +struct MyArc(Arc); +``` + +Here, a meaningful `PartialEq` for `MyArc` requires that `T: PartialEq`. +Therefore, we don't want that bound to be removed from the `impl` of `PartialEq` +for `MyArc`. Instead, we use `#[no_bound(Clone)]` and the resulting `impl`s +will be: + +```rust +// As before: +impl Clone for MyArc { /* .. */ } + +// And `T: PartialEq` is there as expected! +impl PartialEq for MyArc { /* .. */ } +``` + +[proptest]: https://docs.rs/proptest/ +[`Strategy`]: https://docs.rs/proptest/*/proptest/strategy/trait.Strategy.html + +Let's consider this scenario in action with a real world example and create +a wrapper around a trait object of [`Strategy`] in the crate [proptest]: + +```rust +#[derive(Clone, Debug)] +pub struct ArcStrategy<#[no_bound(Clone)] T> { + source: Arc>>> +} + +// Debug is required as seen in these snippets: +pub trait ValueTree { type Value: Debug; } +pub trait Strategy: Debug { type Value: ValueTree; } +``` + +In this case, the generated code will be: + +```rust +impl Clone for ArcStrategy { /* .. */ } +impl Debug for ArcStrategy { /* .. */ } +``` + +We have so far considered a single type parameter. Let's now add another. +We consider a `Refl` encoding in Rust: + +```rust +use std::marker::PhantomData; + +/// A proof term that `S` and `T` are the same type (type identity). +/// This type is only every inhabited when `S` is nominally equivalent to `T`. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[no_bound] +pub struct Id(PhantomData<(*mut S, *mut T)>); + +// .. +``` + +This will generate the following `impl`s: + +```rust +impl Copy for Id { /* .. */ } +impl Clone for Id { /* .. */ } +impl Debug for Id { /* .. */ } +impl Hash for Id { /* .. */ } +impl PartialEq for Id { /* .. */ } +impl Eq for Id { /* .. */ } +impl PartialOrd for Id { /* .. */ } +impl Ord for Id { /* .. */ } +``` + +In this case in particular, we've reduced a lot of clutter as well as +unnecessary typing. + +Why do we need to be able to remove bounds on different parameters +independently? Because their behavior may diverge. Let's consider such +a type where this is the case: + +```rust +#[derive(Clone)] +struct Foo<#[no_bound] S, T> { + bar: Arc, + baz: T, +} +``` + +The generated code in this case is: + +```rust +impl Clone for Foo { /* .. */ } +``` + +With an even more complex scenario we have: + +```rust +#[derive(Clone, PartialEq)] +struct Foo<#[no_bound(Clone)] S, T, #[no_bound(Clone, PartialEq)] U> { + bar: Arc, + baz: T, + quux: PhantomData +} +``` + +and the generated code is: + +```rust +impl Clone for Foo { /* .. */ } +impl Clone for Foo { /* .. */ } +``` + +### `#[no_bound]` is not `#[ignore]` + +Consider the case of `Filter` as in: + +```rust +/// An iterator that filters the elements of `iter` with `predicate`. +#[derive(Clone)] +pub struct Filter { + iter: I, + predicate: P, +} +``` + +This type provides the `impl`: +```rust +impl Debug for Filter +``` + +Notice in particular that `P` lacks the bound `Debug`. +To derive `Debug` instead, you might want to reach for `#[no_bound]` +on `P` in this case as in: + +```rust +#[derive(Clone, Debug)] +pub struct Filter { + iter: I, + predicate: P, +} +``` + +This however, does not work! Why? Because `#[no_bound]` on `P` means that: +> The parameter `P` does not __need__ to satisfy `Trait` for any field +> referencing it to be `Trait` + +It does not mean that: +> Ignore the field `predicate` + +Therefore, deriving `Debug` will not work as above since the deriving +mechanism of `Debug` will try to generate an `impl` which does not work: + +```rust +impl Debug for Filter { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_struct("Filter") + .field("iter", &self.iter) + .field("predicate", &self.predicate) // <-- Not OK! + .finish() + } +} +``` + +Instead the proper `impl`: + +```rust +impl Debug for Filter { + fn fmt(&self, f: &mut Formatter) -> Result { + f.debug_struct("Filter") + .field("iter", &self.iter) + .finish() + } +} +``` + +## Adding bounds on field types with `#[field_bound]` + +To gain more exact control of the bounds put on `impl`s generated by +deriving macros you can also use the `#[field_bound(..)]` attribute. + +A simple example is: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +struct Foo { + #[field_bound] + bar: Bar, + baz: Baz +} +``` + +This will generate the following `impl`s: + +```rust +impl Clone for Foo +where Bar: Clone { /* .. */ } + +impl Clone for Foo +where Bar: PartialEq { /* .. */ } + +impl Clone for Foo +where Bar: PartialEq { /* .. */ } +``` + +We can also apply this to a specific trait `impl`: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +struct Foo { + #[field_bound(Clone)] + bar: Bar, + #[field_bound(Clone)] + baz: Baz +} +``` + +This will generate the following `impl`s: + +```rust +impl Clone for Foo +where Bar: Clone, Baz: Clone { /* .. */ } + +impl Clone for Foo { /* .. */ } + +impl Clone for Foo { /* .. */ } +``` + +We can simplify the definition above to: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +#[field_bound(Clone)] +struct Foo { + bar: Bar, + baz: Baz +} +``` + +or if we want to do this for all derived traits: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +#[field_bound] +struct Foo { + bar: Bar, + baz: Baz +} +``` + +### A note on visibility + +It is important to note that the following generated `impl`: + +```rust +impl Clone for Foo where Bar: Clone { /* .. */ } +``` + +only works if `Foo` is at least as visible as `Bar`. +In particular, a Rust compiler will reject the `impl` above +if `Bar` is private and `Foo` is `pub`. + +## Guidance to custom derive macro authors + +The concepts in this RFC should be taught to derive macro users, by explaining +how the attributes work with derivable traits in the standard library. +These are fairly advanced concepts. As such, they should be deferred to the end +of the book's explanation of Derivable Traits in the appendix section `21.3`. + +For users looking to implement custom derive macros, these concepts should +be explained in conjunction with guides on how to implement these macros. + +Ideally, the `syn` crate, or crates in the same space such as `synstructure`, +should also facilitate handling of the proposed attributes. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The attributes `#[no_bound(..)]` and `#[field_bound(..)]` for controlling +how bounds are used by derive macros for standard library traits and should +be used for those outside in custom derive macros. + +## `#[no_bound(..)]` + +### Grammar + +The attribute `#[no_bound(..)]` can be placed on type definitions directly +(`struct`, `enum`, `union`) or on formal type parameters. The attribute has +the following grammar: + +```enbf +no_bound_attr : "#" "[" "no_bound" no_bound_traits? "]" ; +no_bound_traits : "(" trait_list ","? ")" ; +trait_list : ident | ident "," trait_list ; +``` + +### Semantics - on a formal type parameter + +Formally: Assuming a formal type parameter `P`, and the attribute +`#[no_bound(Trait)]` on `P` for a given specific trait `Trait`, specifying +the attribute `#[derive(Trait)]` shall **NOT** add a bound `P: Trait` to +either the `where`-clause or directly where `P` is brought into scope +(`impl`) in the `impl<.., P, ..> Trait<..> for Type<.., P, ..>` +generated by a derive macro for `Trait`. This does not necessarily mean that +the field which in some way references `P` does not need to implement the +`Trait` in question. + +When `#[no_bound(..)]` contains a comma separated list of traits, +these semantics will apply to each trait referenced but not other traits. + +When `#[no_bound]` is used (with no traits referenced), these rules will +apply to all derived traits. + +#### An example + +Given the following type definition: + +```rust +#[derive(Clone)] +struct Foo<#[no_bound] S, T> { + bar: Arc, + baz: T, +} +``` + +The generated `impl` is: + +```rust +impl // <-- S: Clone is missing +Clone for Foo { /* .. */ } +``` + +### Semantics - on a type + +When `#[no_bound(..)]` is applied directly on a type, this is equivalent to +specifying the identical attribute on each formal type parameter of the type. + +#### An example + +Consider a `Refl` encoding in Rust: + +```rust +use std::marker::PhantomData; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[no_bound] +pub struct Id(PhantomData<(*mut S, *mut T)>); +``` + +The generated `impl`s are: + +```rust +impl Copy for Id { /* .. */ } +impl Clone for Id { /* .. */ } +impl Debug for Id { /* .. */ } +impl Hash for Id { /* .. */ } +impl PartialEq for Id { /* .. */ } +impl Eq for Id { /* .. */ } +impl PartialOrd for Id { /* .. */ } +impl Ord for Id { /* .. */ } +``` + +## `#[field_bound(..)]` + +### Grammar + +The attribute `#[field_bound(..)]` can be placed on type definitions directly +(`struct`, `enum`, `union`) or on their fields. Note in particular that they may +not be specified on variants of `enum`s. The attribute has the following grammar: + +```enbf +field_bound_attr : "#" "[" "field_bound" field_bound_traits? "]" ; +field_bound_traits : "(" trait_list ","? ")" ; +trait_list : ident | ident "," trait_list ; +``` + +### Semantics - on a field + +Formally: Assuming a field `F`, either named or unnamed, of type `FieldType`, +and the attribute `#[field_bound(Trait)]` on `F` for a specific trait `Trait`, +specifying the attribute `#[derive(Trait)]` shall add a bound `FieldType: Trait` +in the `where`-clause in the `impl<..> Trait<..> for Type<..>` generated by a +derive macro for `Trait`. + +When `#[field_bound(..)]` contains a comma separated list of traits, +these semantics will apply to each trait referenced but not other traits. + +When `#[field_bound]` is used (with no traits referenced), these rules +will apply to all derived traits. + +#### An example + +Given the following type definition: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +struct Foo { + #[field_bound(Clone)] + bar: Bar, + baz: Baz +} +``` + +The generated `impl`s are: + +```rust +impl Clone for Foo +where Bar: Clone { /* .. */ } // <-- Note the where clause! + +impl Clone for Foo { /* .. */ } + +impl Clone for Foo { /* .. */ } +``` + +### Semantics - on a type + +When `#[field_bound(..)]` is applied directly on a type, this is equivalent +to specifying the identical attribute on each field of the type. + +#### An example + +Given the following type definition: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +#[field_bound(Clone)] +struct Foo { + bar: Bar, + baz: Baz +} +``` + +The generated `impl`s are: + +```rust +impl Clone for Foo +where Bar: Clone, Baz: Clone { /* .. */ } // <-- Note! + +impl Clone for Foo { /* .. */ } + +impl Clone for Foo { /* .. */ } +``` + +## Warnings + +A warning should be issued if: + +1. `#[no_bound]` is specified on a type definition without type parameters. + +2. `#[no_bound(Trait)]` is specified on a type definition which does not derive + `Trait`. + +3. `#[no_bound]` is specified on a type definition which does not derive any + trait. + +4. `#[field_bound]` is specified on a type without fields. + +5. `#[field_bound]` is specified on a field which is less visible than the type + which contains the field. + +6. `#[field_bound(Trait)]` is specified on a field of a type definition which + does not derive `Trait`. + +7. `#[field_bound]` is specified on a field of a type definition which do not + derive any trait. + +## Deriving of standard library traits + +Deriving any standard library trait will obey the semantics here specified. + +## Custom derive macros + +All custom derive macros as **encouraged** to follow the semantics here +specified so that a consistent experience is maintained in the ecosystem. + +# Drawbacks +[drawbacks]: #drawbacks + +1. It imposes expectations upon custom derive macro authors which they do + not have time for. This can be mitigated by helper crates. + +2. Flexible deriving is a nice-to-have feature but does not enable users to + express things otherwise not expressible. Arguably, the now-derivable + `impl`s should be implemented manually. + +3. The complexity of the derive system is increased. + +# Rationale and alternatives +[alternatives]: #alternatives + +The designs proposed by this RFC aims to make deriving cover `impl`s +that are not derivable today. The design has been considered against +real world scenarios. Some trade-offs and choices are discussed in the +[Unresolved questions][unresolved] section. + +As with any RFC, an alternative is to say that the status quo is good enough, +but for the reasons mentioned in the [motivation], steps should be taken to +make the derive system of Rust more flexible. + +# Prior art +[prior-art]: #prior-art + +[RFC 534]: https://github.com/rust-lang/rfcs/blob/master/text/0534-deriving2derive.md + +The deriving mechanism of Rust was inspired by Haskell, a fact evidenced by +the change in [RFC 534] where `#[deriving(..)]` became `#[derive(..)]`. + +As Haskell does not have a feature similar to Rust's attributes, it is not +possible to configure deriving mechanisms in Haskell. Therefore, there is no +prior art. The features proposed here would be unique to Rust. + +# Unresolved questions +[unresolved]: #unresolved-questions + +## 1. Should `#[no_bound]` be permitted on fields? + +Let's reconsider this example: + +```rust +#[derive(Clone, PartialEq)] +struct Foo<#[no_bound(Clone)] S, T, #[no_bound(Clone, PartialEq)] U> { + bar: Arc, + baz: T, + quux: PhantomData +} +``` + +We could also permit `#[no_bound(..)]` on fields as well and reformulate +the above snippet as: + +```rust +#[derive(Clone, PartialEq)] +struct Foo { + #[no_bound(Clone)] + bar: Arc, + baz: T, + #[no_bound(Clone, PartialEq)] + quux: PhantomData +} +``` + +This is arguably more readable, but hinges on the semantics that bounds are +added by performing name resolution on each field's type and searching for +type parameters in those for usage. This behavior, while not very complex to +encode using visitors the `syn` crate, is not used by derivable traits in the +standard library. Therefore, the experience would not be uniform across traits. + +Such behavior will also handle type macros poorly. Given the type position +macro `Foo` and type `Bar`: + +```rust +macro_rules! Foo { () => { T } } +struct Bar(Foo!()) +``` + +macros have no way to expand `Foo!()`. Arguably, using type position macros +are rare, but for standardization, a more robust approach is probably preferred. +A possibly path ahead is to provide the API proposed in [RFC 2320], in which +case using the field based approach becomes more robust. + +[RFC 2320]: https://github.com/rust-lang/rfcs/pull/2320 + +## 2. Should `#[field_bound]` and `#[no_bound]` be combinable? + +Consider the following snippet: + +```rust +#[derive(Clone, PartialEq, PartialOrd)] +struct Foo { + #[field_bound] + #[no_bound(Clone)] + field: Bar +} +``` + +This could be interpreted as an instruction to provide the following `impl`s: + +```rust +impl Clone for Foo {..} +impl PartialEq for Foo where Bar: PartialEq {..} +impl PartialOrd for Foo where Bar: PartialOrd {..} +``` + +This is currently not proposed as it is deemed unnecessary, but the mechanism +should be considered. + +## 3. Should `#[field_bound]` be named just `#[bound]`? + +The latter is shorter, but less legible, wherefore we've opted to use +`#[field_bound]` at the moment. + +## 4. Should the attributes be prefixed with `derive_`? + +While this makes the attributes more legible on types and reduces the +chance of conflict, prefixing the attributes with `derive_` can become +overly verbose, wherefore the RFC currently does not propose prefixing. +Such prefixing can become especially verbose when applied on type parameters. + +## 5. Permit `field: Vec<#[field_bound] Arc>`? + +If so, `#[bound]` is a more correct name. However, the current thinking +is that this requires parsing changes while also looking weird. This may +be a step too far - in such cases, manual `impl`s are probably better. +For these reasons, the RFC does not propose this mechanism currently. + +## 6. Permit `#[bound(, T: )]`? + +[serde_bound_desc]: https://serde.rs/container-attrs.html#serdebound--t-mytrait + +Last but not least, the crate `serde` allows the attribute +`#[serde(bound = "T: MyBound")]` which replaces the `where` +clause of the `impl` generated by `serde`. This attribute +is [described][serde_bound_desc] as follows: + +> Where-clause for the `Serialize` and `Deserialize` impls. +> This replaces any trait bounds inferred by Serde. + +We could standardize this concept in the form of an attribute +`#[bound(..)]` put on types with a syntax permitting: + ++ Replace bounds on impl of `Clone` and `PartialEq` with `T: Sync` + +```rust +#[bound(Clone, PartialEq, T: Sync)] +``` + ++ Replace bounds on impl of `Clone` with `T: Sync + 'static` + +```rust +#[bound(Clone, T: Sync + 'static)] +``` + ++ Replace bounds on all derived traits with `T: Copy` + +```rust +#[bound(T: Copy)] +``` + ++ No bounds on impl of `Clone` and `PartialEq` + +```rust +#[bound(Clone, PartialEq)] +``` + ++ No bounds on impl of `Clone` + +```rust +#[bound(Clone)] +``` + ++ No bounds on all derived traits: + +```rust +#[bound] +``` + +The syntax `TyVar: Bound` is however not allowed in attributes currently. +Changing this would require a language change. Another option is to quote the +bound as `"TyVar: Bound"` as done by `serde`. This requires no larger changes, +but is brittle, strange, and would require of syntax highlighters to understand +`#[bound]` specially. Therefore, a more permissible attribute syntax might be a +good thing and can have positive effects elsewhere. + +[A real world example]: https://github.com/ppedrot/kravanenn/blob/61f089e2091d1f0c4eb57b2617532e7bee63508d/src/ocaml/values.rs#L10 + +[A real world example] of how `serde`'s attribute is used is: +```rust +#[derive(Debug, Clone, DeserializeState, Hash, PartialEq, Eq)] +#[serde(deserialize_state = "Seed<'de>")] +#[serde(bound(deserialize = + "T: serde::de::DeserializeState<'de, Seed<'de>> + Send + Sync + 'static"))] +pub enum List { + Nil, + Cons(#[serde(deserialize_state)] ORef<(T, List)>), +} +``` + +with `#[bound]`, this is rewritten as: + +```rust +#[derive(Debug, Clone, DeserializeState, Hash, PartialEq, Eq)] +#[serde(deserialize_state = "Seed<'de>")] +#[bound(Deserialize, + T: serde::de::DeserializeState<'de, Seed<'de>> + Send + Sync + 'static)] +pub enum List { + Nil, + Cons(#[serde(deserialize_state)] ORef<(T, List)>), +} +``` \ No newline at end of file From 0d2afeeef5f426c02987ff0da68b8c94b635e145 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Feb 2018 14:05:51 +0100 Subject: [PATCH 2/9] rfc, derive_bound_control: fixes + warnings -> errors" --- text/0000-derive-bound-control.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index e04d9e498b2..c4f29216984 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -31,7 +31,7 @@ Unfortunately, there are many cases where deriving fails to produce the code indented by manual implementations. Either the `impl`s produced are too restrictive by imposing bounds that shouldn't be there, which is solved by `#[no_bound(..)]`, or not enough bounds are imposed. When the latter is the -case, deriving may fail entirely. This is solved by `#[bound(..)]`. +case, deriving may fail entirely. This is solved by `#[field_bound(..)]`. The crate `serde` provides the attribute `#[serde(bound = "T: MyTrait")]`. This can be used solve the same issues as in this RFC. This RFC proposes a @@ -523,9 +523,9 @@ impl Clone for Foo { /* .. */ } impl Clone for Foo { /* .. */ } ``` -## Warnings +## Errors -A warning should be issued if: +An error should be issued if: 1. `#[no_bound]` is specified on a type definition without type parameters. @@ -537,15 +537,22 @@ A warning should be issued if: 4. `#[field_bound]` is specified on a type without fields. -5. `#[field_bound]` is specified on a field which is less visible than the type - which contains the field. +5. `#[field_bound]` is specified on a field with a type which is less visible + than the type which contains the field. If `#[field_bound]` is applied on + the type, then this rule applied for all fields of the type. 6. `#[field_bound(Trait)]` is specified on a field of a type definition which does not derive `Trait`. -7. `#[field_bound]` is specified on a field of a type definition which do not +7. `#[field_bound]` is specified on a field of a type definition which does not derive any trait. +8. `#[field_bound(Trait)]` is specified on a type definition and `Trait` is + registered for deriving by a custom macro which specifies + `#[proc_macro_derive(Trait, attributes())]` where `` + does not mention `field_bound`. If `#[field_bound]` is specified instead, + then this applies to all traits derived. This also applies to `#[no_bound]`. + ## Deriving of standard library traits Deriving any standard library trait will obey the semantics here specified. From 14c41601b59479c3b6d06d7ad9afd2b75ff03a8c Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Feb 2018 16:10:40 +0100 Subject: [PATCH 3/9] rfc, derive_bound_control: prefix with derive_ + discuss prior art" --- text/0000-derive-bound-control.md | 293 ++++++++++++++++++------------ 1 file changed, 172 insertions(+), 121 deletions(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index c4f29216984..c7ede53aebf 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -9,13 +9,14 @@ 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 `#[no_bound(Trait)]` and `#[field_bound(Trait)]`. +the two attributes `#[derive_no_bound(Trait)]` and +`#[derive_field_bound(Trait)]`. -The semantics of `#[no_bound(Trait)]` for a type parameter `P` are: +The semantics of `#[derive_no_bound(Trait)]` for a type parameter `P` are: > The type parameter `P` does not need to satisfy `Trait` for any field > referencing it to be `Trait` -The semantics of `#[field_bound(Trait)]` on a field are that the type +The semantics of `#[derive_field_bound(Trait)]` on a field are that the type of the field is added to the `where`-clause of the referenced `Trait` as: `FieldType: Trait`. @@ -27,11 +28,12 @@ pain by significantly reducing boilerplate in many cases. Deriving also allows readers of code to easily see when a bunch of simple delegating `impl`s are defined instead of reading such boilerplate as manual `impl`s. -Unfortunately, there are many cases where deriving fails to produce the code -indented by manual implementations. Either the `impl`s produced are too -restrictive by imposing bounds that shouldn't be there, which is solved by -`#[no_bound(..)]`, or not enough bounds are imposed. When the latter is the -case, deriving may fail entirely. This is solved by `#[field_bound(..)]`. +Unfortunately, there are many cases where deriving fails to produce +the code intended by manual implementations. Either the `impl`s produced +are too restrictive by imposing bounds that shouldn't be there, which is +solved by `#[derive_no_bound(..)]`, or not enough bounds are imposed. +When the latter is the case, deriving may fail entirely. This is solved +by `#[derive_field_bound(..)]`. The crate `serde` provides the attribute `#[serde(bound = "T: MyTrait")]`. This can be used solve the same issues as in this RFC. This RFC proposes a @@ -43,21 +45,21 @@ in all of the ecosystem. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -## Removing bounds in derive with `#[no_bound]` +## Removing bounds in derive with `#[derive_no_bound]` Let's consider a simple new-type around an `Arc`: ```rust #[derive(Clone)] -struct MyArc<#[no_bound] T>(Arc); +struct MyArc<#[derive_no_bound] T>(Arc); ``` -or, to apply `#[no_bound]` to all type parameters, which is in this case +or, to apply `#[derive_no_bound]` to all type parameters, which is in this case equivalent: ```rust #[derive(Clone)] -#[no_bound] +#[derive_no_bound] struct MyArc(Arc); ``` @@ -68,31 +70,32 @@ The resulting `impl` will be of the form: impl Clone for MyArc { /* .. */ } ``` -We see that `#[no_bound]` on `T` is an instruction to the derive macro for -`Clone` that it should not add `T: Clone`. This applies to any trait being +We see that `#[derive_no_bound]` on `T` is an instruction to the derive macro +for `Clone` that it should not add `T: Clone`. This applies to any trait being derived and not just `Clone`. This works since `Arc: Clone` holds regardless of whether `T: Clone` or not. -But what if you want to differentiate between the deriving behavior of various -traits? Let's derive another trait, `PartialEq`, but still use `#[no_bound(..)]`: +But what if you want to differentiate between the deriving behavior +of various traits? Let's derive another trait, `PartialEq`, but still +use `#[derive_no_bound(..)]`: ```rust #[derive(Clone, PartialEq)] -struct MyArc<#[no_bound(Clone)] T>(Arc); +struct MyArc<#[derive_no_bound(Clone)] T>(Arc); ``` We can equivalently write: ```rust #[derive(Clone, PartialEq)] -#[no_bound(Clone)] +#[derive_no_bound(Clone)] struct MyArc(Arc); ``` Here, a meaningful `PartialEq` for `MyArc` requires that `T: PartialEq`. Therefore, we don't want that bound to be removed from the `impl` of `PartialEq` -for `MyArc`. Instead, we use `#[no_bound(Clone)]` and the resulting `impl`s -will be: +for `MyArc`. Instead, we use `#[derive_no_bound(Clone)]` and the resulting +`impl`s will be: ```rust // As before: @@ -110,7 +113,7 @@ a wrapper around a trait object of [`Strategy`] in the crate [proptest]: ```rust #[derive(Clone, Debug)] -pub struct ArcStrategy<#[no_bound(Clone)] T> { +pub struct ArcStrategy<#[derive_no_bound(Clone)] T> { source: Arc>>> } @@ -135,7 +138,7 @@ use std::marker::PhantomData; /// A proof term that `S` and `T` are the same type (type identity). /// This type is only every inhabited when `S` is nominally equivalent to `T`. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[no_bound] +#[derive_no_bound] pub struct Id(PhantomData<(*mut S, *mut T)>); // .. @@ -163,7 +166,7 @@ a type where this is the case: ```rust #[derive(Clone)] -struct Foo<#[no_bound] S, T> { +struct Foo<#[derive_no_bound] S, T> { bar: Arc, baz: T, } @@ -179,7 +182,9 @@ With an even more complex scenario we have: ```rust #[derive(Clone, PartialEq)] -struct Foo<#[no_bound(Clone)] S, T, #[no_bound(Clone, PartialEq)] U> { +struct Foo<#[derive_no_bound(Clone)] S, + T, + #[derive_no_bound(Clone, PartialEq)] U> { bar: Arc, baz: T, quux: PhantomData @@ -193,7 +198,7 @@ impl Clone for Foo { /* .. */ } impl Clone for Foo { /* .. */ } ``` -### `#[no_bound]` is not `#[ignore]` +### `#[derive_no_bound]` is not `#[derive_ignore]` Consider the case of `Filter` as in: @@ -212,18 +217,18 @@ impl Debug for Filter ``` Notice in particular that `P` lacks the bound `Debug`. -To derive `Debug` instead, you might want to reach for `#[no_bound]` +To derive `Debug` instead, you might want to reach for `#[derive_no_bound]` on `P` in this case as in: ```rust #[derive(Clone, Debug)] -pub struct Filter { +pub struct Filter { iter: I, predicate: P, } ``` -This however, does not work! Why? Because `#[no_bound]` on `P` means that: +This however, does not work! Why? Because `#[derive_no_bound]` on `P` means that: > The parameter `P` does not __need__ to satisfy `Trait` for any field > referencing it to be `Trait` @@ -256,17 +261,17 @@ impl Debug for Filter { } ``` -## Adding bounds on field types with `#[field_bound]` +## Adding bounds on field types with `#[derive_field_bound]` To gain more exact control of the bounds put on `impl`s generated by -deriving macros you can also use the `#[field_bound(..)]` attribute. +deriving macros you can also use the `#[derive_field_bound(..)]` attribute. A simple example is: ```rust #[derive(Clone, PartialEq, PartialOrd)] struct Foo { - #[field_bound] + #[derive_field_bound] bar: Bar, baz: Baz } @@ -290,9 +295,9 @@ We can also apply this to a specific trait `impl`: ```rust #[derive(Clone, PartialEq, PartialOrd)] struct Foo { - #[field_bound(Clone)] + #[derive_field_bound(Clone)] bar: Bar, - #[field_bound(Clone)] + #[derive_field_bound(Clone)] baz: Baz } ``` @@ -312,7 +317,7 @@ We can simplify the definition above to: ```rust #[derive(Clone, PartialEq, PartialOrd)] -#[field_bound(Clone)] +#[derive_field_bound(Clone)] struct Foo { bar: Bar, baz: Baz @@ -323,7 +328,7 @@ or if we want to do this for all derived traits: ```rust #[derive(Clone, PartialEq, PartialOrd)] -#[field_bound] +#[derive_field_bound] struct Foo { bar: Bar, baz: Baz @@ -358,20 +363,20 @@ should also facilitate handling of the proposed attributes. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -The attributes `#[no_bound(..)]` and `#[field_bound(..)]` for controlling -how bounds are used by derive macros for standard library traits and should -be used for those outside in custom derive macros. +The attributes `#[derive_no_bound(..)]` and `#[derive_field_bound(..)]` for +controlling how bounds are used by derive macros for standard library traits +and should be used for those outside in custom derive macros. -## `#[no_bound(..)]` +## `#[derive_no_bound(..)]` ### Grammar -The attribute `#[no_bound(..)]` can be placed on type definitions directly -(`struct`, `enum`, `union`) or on formal type parameters. The attribute has -the following grammar: +The attribute `#[derive_no_bound(..)]` can be placed on type definitions +directly (`struct`, `enum`, `union`) or on formal type parameters. The attribute +has the following grammar: ```enbf -no_bound_attr : "#" "[" "no_bound" no_bound_traits? "]" ; +no_bound_attr : "#" "[" "derive_no_bound" no_bound_traits? "]" ; no_bound_traits : "(" trait_list ","? ")" ; trait_list : ident | ident "," trait_list ; ``` @@ -379,18 +384,18 @@ trait_list : ident | ident "," trait_list ; ### Semantics - on a formal type parameter Formally: Assuming a formal type parameter `P`, and the attribute -`#[no_bound(Trait)]` on `P` for a given specific trait `Trait`, specifying -the attribute `#[derive(Trait)]` shall **NOT** add a bound `P: Trait` to -either the `where`-clause or directly where `P` is brought into scope +`#[derive_no_bound(Trait)]` on `P` for a given specific trait `Trait`, +specifying the attribute `#[derive(Trait)]` shall **NOT** add a bound `P: Trait` +to either the `where`-clause or directly where `P` is brought into scope (`impl`) in the `impl<.., P, ..> Trait<..> for Type<.., P, ..>` generated by a derive macro for `Trait`. This does not necessarily mean that the field which in some way references `P` does not need to implement the `Trait` in question. -When `#[no_bound(..)]` contains a comma separated list of traits, +When `#[derive_no_bound(..)]` contains a comma separated list of traits, these semantics will apply to each trait referenced but not other traits. -When `#[no_bound]` is used (with no traits referenced), these rules will +When `#[derive_no_bound]` is used (with no traits referenced), these rules will apply to all derived traits. #### An example @@ -399,7 +404,7 @@ Given the following type definition: ```rust #[derive(Clone)] -struct Foo<#[no_bound] S, T> { +struct Foo<#[derive_no_bound] S, T> { bar: Arc, baz: T, } @@ -414,8 +419,8 @@ Clone for Foo { /* .. */ } ### Semantics - on a type -When `#[no_bound(..)]` is applied directly on a type, this is equivalent to -specifying the identical attribute on each formal type parameter of the type. +When `#[derive_no_bound(..)]` is applied directly on a type, this is equivalent +to specifying the identical attribute on each formal type parameter of the type. #### An example @@ -425,7 +430,7 @@ Consider a `Refl` encoding in Rust: use std::marker::PhantomData; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[no_bound] +#[derive_no_bound] pub struct Id(PhantomData<(*mut S, *mut T)>); ``` @@ -442,32 +447,34 @@ impl PartialOrd for Id { /* .. */ } impl Ord for Id { /* .. */ } ``` -## `#[field_bound(..)]` +## `#[derive_field_bound(..)]` ### Grammar -The attribute `#[field_bound(..)]` can be placed on type definitions directly -(`struct`, `enum`, `union`) or on their fields. Note in particular that they may -not be specified on variants of `enum`s. The attribute has the following grammar: +The attribute `#[derive_field_bound(..)]` can be placed on type definitions +directly (`struct`, `enum`, `union`) or on their fields. Note in particular +that they may not be specified on variants of `enum`s. The attribute has the +following grammar: ```enbf -field_bound_attr : "#" "[" "field_bound" field_bound_traits? "]" ; +field_bound_attr : "#" "[" "derive_field_bound" field_bound_traits? "]" ; field_bound_traits : "(" trait_list ","? ")" ; trait_list : ident | ident "," trait_list ; ``` ### Semantics - on a field -Formally: Assuming a field `F`, either named or unnamed, of type `FieldType`, -and the attribute `#[field_bound(Trait)]` on `F` for a specific trait `Trait`, -specifying the attribute `#[derive(Trait)]` shall add a bound `FieldType: Trait` -in the `where`-clause in the `impl<..> Trait<..> for Type<..>` generated by a -derive macro for `Trait`. +Formally: Assuming a field `F`, either named or unnamed, of +type `FieldType`, and the attribute `#[derive_field_bound(Trait)]` +on `F` for a specific trait`Trait`, specifying the attribute +`#[derive(Trait)]` shall add a bound `FieldType: Trait` in the +`where`-clause in the `impl<..> Trait<..> for Type<..>` generated +by a derive macro for `Trait`. -When `#[field_bound(..)]` contains a comma separated list of traits, +When `#[derive_field_bound(..)]` contains a comma separated list of traits, these semantics will apply to each trait referenced but not other traits. -When `#[field_bound]` is used (with no traits referenced), these rules +When `#[derive_field_bound]` is used (with no traits referenced), these rules will apply to all derived traits. #### An example @@ -477,7 +484,7 @@ Given the following type definition: ```rust #[derive(Clone, PartialEq, PartialOrd)] struct Foo { - #[field_bound(Clone)] + #[derive_field_bound(Clone)] bar: Bar, baz: Baz } @@ -496,8 +503,8 @@ impl Clone for Foo { /* .. */ } ### Semantics - on a type -When `#[field_bound(..)]` is applied directly on a type, this is equivalent -to specifying the identical attribute on each field of the type. +When `#[derive_field_bound(..)]` is applied directly on a type, this is +equivalent to specifying the identical attribute on each field of the type. #### An example @@ -505,7 +512,7 @@ Given the following type definition: ```rust #[derive(Clone, PartialEq, PartialOrd)] -#[field_bound(Clone)] +#[derive_field_bound(Clone)] struct Foo { bar: Bar, baz: Baz @@ -527,31 +534,32 @@ impl Clone for Foo { /* .. */ } An error should be issued if: -1. `#[no_bound]` is specified on a type definition without type parameters. +1. `#[derive_no_bound]` is specified on a type definition without type parameters. -2. `#[no_bound(Trait)]` is specified on a type definition which does not derive - `Trait`. +2. `#[derive_no_bound(Trait)]` is specified on a type definition which does + not derive `Trait`. -3. `#[no_bound]` is specified on a type definition which does not derive any - trait. +3. `#[derive_no_bound]` is specified on a type definition which does not derive + any trait. -4. `#[field_bound]` is specified on a type without fields. +4. `#[derive_field_bound]` is specified on a type without fields. -5. `#[field_bound]` is specified on a field with a type which is less visible - than the type which contains the field. If `#[field_bound]` is applied on - the type, then this rule applied for all fields of the type. +5. `#[derive_field_bound]` is specified on a field with a type which is less + visible than the type which contains the field. If `#[derive_field_bound]` + is applied on the type, then this rule applied for all fields of the type. -6. `#[field_bound(Trait)]` is specified on a field of a type definition which - does not derive `Trait`. +6. `#[derive_field_bound(Trait)]` is specified on a field of a type definition + which does not derive `Trait`. -7. `#[field_bound]` is specified on a field of a type definition which does not - derive any trait. +7. `#[derive_field_bound]` is specified on a field of a type definition which + does not derive any trait. -8. `#[field_bound(Trait)]` is specified on a type definition and `Trait` is - registered for deriving by a custom macro which specifies +8. `#[derive_field_bound(Trait)]` is specified on a type definition and `Trait` + is registered for deriving by a custom macro which specifies `#[proc_macro_derive(Trait, attributes())]` where `` - does not mention `field_bound`. If `#[field_bound]` is specified instead, - then this applies to all traits derived. This also applies to `#[no_bound]`. + does not mention `derive_field_bound`. If `#[derive_field_bound]` is + specified instead, then this applies to all traits derived. + This also applies to `#[derive_no_bound]`. ## Deriving of standard library traits @@ -586,9 +594,16 @@ As with any RFC, an alternative is to say that the status quo is good enough, but for the reasons mentioned in the [motivation], steps should be taken to make the derive system of Rust more flexible. +One alternative design of this RFC would be to only permit the form +`#[derive_bound(, T: )]` and then call it +a day since the form is strictly more general. However, this form is +also less automating for a lot of cases. + # Prior art [prior-art]: #prior-art +## Haskell + [RFC 534]: https://github.com/rust-lang/rfcs/blob/master/text/0534-deriving2derive.md The deriving mechanism of Rust was inspired by Haskell, a fact evidenced by @@ -596,42 +611,59 @@ the change in [RFC 534] where `#[deriving(..)]` became `#[derive(..)]`. As Haskell does not have a feature similar to Rust's attributes, it is not possible to configure deriving mechanisms in Haskell. Therefore, there is no -prior art. The features proposed here would be unique to Rust. +prior art there. The features proposed here would be unique to Rust. + +## The [`derivative`] crate + +[`derivative`]: https://mcarton.github.io/rust-derivative/ + +There is some prior art among crates in Rust. The crate [`derivative`] provides +the ability to customize the deriving mechanisms of derivable standard library +traits. We will now discuss the customizations in that crate compared to this +RFC. + +The attribute form `#[derivative(Default(bound=""))]` is supported by the +`#[derive_no_bound]` attribute while the more general form is supported by +the form `#[bound(, T: )]`, which is discussed as a +possible extension of this RFC in the [Unresolved questions][unresolved]. +This more general form is also supported by the `serde` crate. # Unresolved questions [unresolved]: #unresolved-questions -## 1. Should `#[no_bound]` be permitted on fields? +## 1. Should `#[derive_no_bound]` be permitted on fields? Let's reconsider this example: ```rust #[derive(Clone, PartialEq)] -struct Foo<#[no_bound(Clone)] S, T, #[no_bound(Clone, PartialEq)] U> { +struct Foo<#[derive_no_bound(Clone)] S, + T, + #[derive_no_bound(Clone, PartialEq)] U> { bar: Arc, baz: T, quux: PhantomData } ``` -We could also permit `#[no_bound(..)]` on fields as well and reformulate -the above snippet as: +We could also permit `#[derive_no_bound(..)]` on fields as well and +reformulate the above snippet as: ```rust #[derive(Clone, PartialEq)] struct Foo { - #[no_bound(Clone)] + #[derive_no_bound(Clone)] bar: Arc, baz: T, - #[no_bound(Clone, PartialEq)] + #[derive_no_bound(Clone, PartialEq)] quux: PhantomData } ``` This is arguably more readable, but hinges on the semantics that bounds are -added by performing name resolution on each field's type and searching for -type parameters in those for usage. This behavior, while not very complex to -encode using visitors the `syn` crate, is not used by derivable traits in the +added by performing name resolution on each field's type and searching for type +parameters in those for usage. This behavior, while not very complex to encode +using visitors from the `syn` crate, is not used by derivable traits in the standard library. Therefore, the experience would not be uniform across traits. Such behavior will also handle type macros poorly. Given the type position @@ -649,15 +681,19 @@ case using the field based approach becomes more robust. [RFC 2320]: https://github.com/rust-lang/rfcs/pull/2320 -## 2. Should `#[field_bound]` and `#[no_bound]` be combinable? +There's also the question of whether interpretations of `#[derive_no_bound]` +on fields is legible and intuitive, which misunderstandings so far during +development of this RFC has shown is not unlikely. + +## 2. Should `#[derive_field_bound]` and `#[derive_no_bound]` be combinable? Consider the following snippet: ```rust #[derive(Clone, PartialEq, PartialOrd)] struct Foo { - #[field_bound] - #[no_bound(Clone)] + #[derive_field_bound] + #[derive_no_bound(Clone)] field: Bar } ``` @@ -673,26 +709,27 @@ impl PartialOrd for Foo where Bar: PartialOrd {..} This is currently not proposed as it is deemed unnecessary, but the mechanism should be considered. -## 3. Should `#[field_bound]` be named just `#[bound]`? +## 3. Should `#[derive_field_bound]` be named just `#[derive_bound]`? The latter is shorter, but less legible, wherefore we've opted to use -`#[field_bound]` at the moment. +`#[derive_field_bound]` at the moment. -## 4. Should the attributes be prefixed with `derive_`? +## 4. Should the attributes not be prefixed with `derive_`? -While this makes the attributes more legible on types and reduces the -chance of conflict, prefixing the attributes with `derive_` can become -overly verbose, wherefore the RFC currently does not propose prefixing. -Such prefixing can become especially verbose when applied on type parameters. +Prefixing with `derive_` is more legible and reduces the chance of conflict. +But it is also more verbose, especially when applied on type parameters. +The current thinking is that readability takes precedence over reducing +possible verbosity. In any case, prefixing with `derive_` is far less verbose +than manually implementing the trait. -## 5. Permit `field: Vec<#[field_bound] Arc>`? +## 5. Permit `field: Vec<#[derive_field_bound] Arc>`? -If so, `#[bound]` is a more correct name. However, the current thinking +If so, `#[derive_bound]` is a more correct name. However, the current thinking is that this requires parsing changes while also looking weird. This may be a step too far - in such cases, manual `impl`s are probably better. For these reasons, the RFC does not propose this mechanism currently. -## 6. Permit `#[bound(, T: )]`? +## 6. Permit `#[derive_bound(, T: )]`? [serde_bound_desc]: https://serde.rs/container-attrs.html#serdebound--t-mytrait @@ -705,50 +742,50 @@ is [described][serde_bound_desc] as follows: > This replaces any trait bounds inferred by Serde. We could standardize this concept in the form of an attribute -`#[bound(..)]` put on types with a syntax permitting: +`#[derive_bound(..)]` put on types with a syntax permitting: + Replace bounds on impl of `Clone` and `PartialEq` with `T: Sync` ```rust -#[bound(Clone, PartialEq, T: Sync)] +#[derive_bound(Clone, PartialEq, T: Sync)] ``` + Replace bounds on impl of `Clone` with `T: Sync + 'static` ```rust -#[bound(Clone, T: Sync + 'static)] +#[derive_bound(Clone, T: Sync + 'static)] ``` + Replace bounds on all derived traits with `T: Copy` ```rust -#[bound(T: Copy)] +#[derive_bound(T: Copy)] ``` + No bounds on impl of `Clone` and `PartialEq` ```rust -#[bound(Clone, PartialEq)] +#[derive_bound(Clone, PartialEq)] ``` + No bounds on impl of `Clone` ```rust -#[bound(Clone)] +#[derive_bound(Clone)] ``` + No bounds on all derived traits: ```rust -#[bound] +#[derive_bound] ``` The syntax `TyVar: Bound` is however not allowed in attributes currently. Changing this would require a language change. Another option is to quote the bound as `"TyVar: Bound"` as done by `serde`. This requires no larger changes, but is brittle, strange, and would require of syntax highlighters to understand -`#[bound]` specially. Therefore, a more permissible attribute syntax might be a -good thing and can have positive effects elsewhere. +`#[derive_bound]` specially. Therefore, a more permissible attribute syntax +might be a good thing and can have positive effects elsewhere. [A real world example]: https://github.com/ppedrot/kravanenn/blob/61f089e2091d1f0c4eb57b2617532e7bee63508d/src/ocaml/values.rs#L10 @@ -757,7 +794,7 @@ good thing and can have positive effects elsewhere. #[derive(Debug, Clone, DeserializeState, Hash, PartialEq, Eq)] #[serde(deserialize_state = "Seed<'de>")] #[serde(bound(deserialize = - "T: serde::de::DeserializeState<'de, Seed<'de>> + Send + Sync + 'static"))] + "T: DeserializeState<'de, Seed<'de>> + Send + Sync + 'static"))] pub enum List { Nil, Cons(#[serde(deserialize_state)] ORef<(T, List)>), @@ -769,10 +806,24 @@ with `#[bound]`, this is rewritten as: ```rust #[derive(Debug, Clone, DeserializeState, Hash, PartialEq, Eq)] #[serde(deserialize_state = "Seed<'de>")] -#[bound(Deserialize, - T: serde::de::DeserializeState<'de, Seed<'de>> + Send + Sync + 'static)] +#[derive_bound(Deserialize, + T: DeserializeState<'de, Seed<'de>> + Send + Sync + 'static)] pub enum List { Nil, Cons(#[serde(deserialize_state)] ORef<(T, List)>), } -``` \ No newline at end of file +``` + +## 7. Permit `#[derive_no_bound()]` and `#[derive_field_bound()]`? + +If we consider the exact syntax `#[derive_no_bound()]`, there are +two interpretations that come to mind: + +1. Equivalent to `#[derive_no_bound]`. +2. Equivalent to *"ignore the bound in an empty set of traits"*. + +The 2nd interpretation is useful for macros, while the 1st may make more +sense for a reader, which would just write `#[derive_no_bound]`. Since +the 2nd interpretation is more useful, it is probably more appropriate. +To avoid the confusion for users who write this manually, a warning could +be issued which macros may supress. \ No newline at end of file From 3c50f3fe622554f3f77153bdc8f05f98b54c2633 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Feb 2018 17:07:29 +0100 Subject: [PATCH 4/9] rfc, derive_bound_control: clarify need for attr grammar change for #[derive_bound(..)]" --- text/0000-derive-bound-control.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index c7ede53aebf..a99de0dcf08 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -781,11 +781,13 @@ We could standardize this concept in the form of an attribute ``` The syntax `TyVar: Bound` is however not allowed in attributes currently. -Changing this would require a language change. Another option is to quote the -bound as `"TyVar: Bound"` as done by `serde`. This requires no larger changes, -but is brittle, strange, and would require of syntax highlighters to understand -`#[derive_bound]` specially. Therefore, a more permissible attribute syntax -might be a good thing and can have positive effects elsewhere. +Changing this would require a change to the attribute grammar to permit: +`ident ":" bound`. + +Another option is to quote the bound as `"TyVar: Bound"` as done by `serde`. +This requires no larger changes, but is brittle, strange, and would require of +syntax highlighters to understand `#[derive_bound]` specially. Therefore, a more +permissible attribute syntax allowing subsets of bounds, expressions and types might be a good thing and can have positive effects elsewhere. [A real world example]: https://github.com/ppedrot/kravanenn/blob/61f089e2091d1f0c4eb57b2617532e7bee63508d/src/ocaml/values.rs#L10 From e97c6daa17d5a96113813320ffd409505c61a95b Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 28 Feb 2018 17:20:14 +0100 Subject: [PATCH 5/9] rfc, derive_bound_control: someone forget to tell me it's 2018 now..." --- text/0000-derive-bound-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index a99de0dcf08..7851cac115d 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -1,5 +1,5 @@ - Feature Name: derive_bound_control -- Start Date: 2017-02-26 +- Start Date: 2018-02-26 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) From 008eda7a4aba88bc9e91f28f712f0f28ad7a1166 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 1 Mar 2018 16:06:51 +0100 Subject: [PATCH 6/9] rfc, derive_bound_control: fix copy errors, thanks @ExpHP" --- text/0000-derive-bound-control.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index 7851cac115d..a2dfab65f80 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -496,9 +496,9 @@ The generated `impl`s are: impl Clone for Foo where Bar: Clone { /* .. */ } // <-- Note the where clause! -impl Clone for Foo { /* .. */ } +impl PartialEq for Foo { /* .. */ } -impl Clone for Foo { /* .. */ } +impl PartialOrd for Foo { /* .. */ } ``` ### Semantics - on a type @@ -525,9 +525,9 @@ The generated `impl`s are: impl Clone for Foo where Bar: Clone, Baz: Clone { /* .. */ } // <-- Note! -impl Clone for Foo { /* .. */ } +impl PartialEq for Foo { /* .. */ } -impl Clone for Foo { /* .. */ } +impl PartialOrd for Foo { /* .. */ } ``` ## Errors From 844c32a32d6b6900fba9773732a4d86351f8aec1 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Mar 2018 01:14:32 +0200 Subject: [PATCH 7/9] rfc, derive_bound_control: notes on #[structural_match] --- text/0000-derive-bound-control.md | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index a2dfab65f80..8cc349c840a 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -570,6 +570,71 @@ Deriving any standard library trait will obey the semantics here specified. All custom derive macros as **encouraged** to follow the semantics here specified so that a consistent experience is maintained in the ecosystem. +## Structural equality + +[RFC 1445]: https://github.com/rust-lang/rfcs/pull/1445 + +[RFC 1445] and Rust currently adds the attribute `#[structural_match]` when +a type definition has `#[derive(PartialEq, Eq)]` on it and all the fields of +the type are also `#[structural_match]`. + +To use `const` as the pattern in a match arm, it has to be of a type that is +`#[structural_match]`. If it is not, as in the example below: + +```rust +fn main() { + use std::marker::PhantomData; + + pub const PD: PhantomData = PhantomData; + + match PhantomData { + PD => {} + } +} +``` + +... an error will be emitted saying that: + +```rust +error: to use a constant of type `std::marker::PhantomData` in a pattern, `std::marker::PhantomData` must be annotated with `#[derive(PartialEq, Eq)]` + --> src/main.rs:7:9 + | +7 | PD => {} + | ^^ +``` + +With respect to `#[structural_match]` this RFC does two things: + +["structural match check"]: https://github.com/rust-lang/rust/blob/58e1234cddd996378cb9df6bed537b9c08a6df73/src/libsyntax/ext/derive.rs#L73-L76 + +1. The ["structural match check"] will ignore `#[derive_no_bound]` and + `#[derive_field_bound]`. + +2. `PhantomData` will be defined as: + +```rust +#[derive(PartialEq, Eq, ...)] +#[derive_no_bound] +#[lang_item = "phantom_data"] +struct PhantomData; +``` + +With the new definition in (2), the error above will not be emitted and the +program will be accepted. + +This change is does not move us from structural matching. A `PhantomData` +can be compared with another `PhantomData` by doing a `memcmp` logically. +This is so since a zero sized type does not exist in memory and so our logical +`memcmp` would always return `true`. Thus, two `PhantomData::`s are +structurally equal, and therefore `#[structural_match]` safely applies. +Note however that `T != U => ! [PhantomData: PartialEq]`. + +All type definitions with type parameters must use those parameters. +In the end, all valid uses of `#[derive_no_bound(PartialEq, Eq)]` must at +some depth involve either ignoring a field in `PartialEq`, in which case +`#[structural_match]` does not apply, or must involve a `PhantomData` +for which `#[structural_match]` did apply safely. + # Drawbacks [drawbacks]: #drawbacks From 5eba3d49e764ef17b7b3dde48ef6c95b0a69a12f Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Mar 2018 13:50:11 +0200 Subject: [PATCH 8/9] rfc, derive_bound_control: fix nits @nox found. --- text/0000-derive-bound-control.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index 8cc349c840a..754cd1db4f3 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -585,10 +585,10 @@ To use `const` as the pattern in a match arm, it has to be of a type that is fn main() { use std::marker::PhantomData; - pub const PD: PhantomData = PhantomData; + pub const BOO: PhantomData = PhantomData; match PhantomData { - PD => {} + BOO => {} } } ``` @@ -599,8 +599,8 @@ fn main() { error: to use a constant of type `std::marker::PhantomData` in a pattern, `std::marker::PhantomData` must be annotated with `#[derive(PartialEq, Eq)]` --> src/main.rs:7:9 | -7 | PD => {} - | ^^ +7 | BOO => {} + | ^^^ ``` With respect to `#[structural_match]` this RFC does two things: @@ -619,10 +619,10 @@ With respect to `#[structural_match]` this RFC does two things: struct PhantomData; ``` -With the new definition in (2), the error above will not be emitted and the -program will be accepted. +With this new definition of `PhantomData`, the error above will not be +emitted and the example program will be accepted. -This change is does not move us from structural matching. A `PhantomData` +This change does not move us from structural matching. A `PhantomData` can be compared with another `PhantomData` by doing a `memcmp` logically. This is so since a zero sized type does not exist in memory and so our logical `memcmp` would always return `true`. Thus, two `PhantomData::`s are From b6cd10062d7b42709e5869f68c1c69242279f025 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 28 Mar 2018 12:01:55 +0200 Subject: [PATCH 9/9] rfc, derive_bound_control: errors or warnings --- text/0000-derive-bound-control.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/text/0000-derive-bound-control.md b/text/0000-derive-bound-control.md index 754cd1db4f3..7b502e4b2d1 100644 --- a/text/0000-derive-bound-control.md +++ b/text/0000-derive-bound-control.md @@ -531,6 +531,7 @@ impl PartialOrd for Foo { /* .. */ } ``` ## Errors +[errors]: #errors An error should be issued if: @@ -893,4 +894,11 @@ The 2nd interpretation is useful for macros, while the 1st may make more sense for a reader, which would just write `#[derive_no_bound]`. Since the 2nd interpretation is more useful, it is probably more appropriate. To avoid the confusion for users who write this manually, a warning could -be issued which macros may supress. \ No newline at end of file +be issued which macros may supress. + +## 8. Should the errors raised be warnings instead? + +Some, or most of the errors in the [errors] section of the +[reference-level explanation] could be warnings instead of errors to facilitate +for macro authors. This decision can be deferred to stabilization instead, or +even for post stabilization as errors of this kind can be lowered to warnings. \ No newline at end of file