I made a library derive-aliases which extends the derive macro's syntax with ..Alias that expands into user-defined aliases:
#[derive(Debug, ..Ord, ..Copy)]
struct User;
Users define their own aliases:
// Define the aliases
derive_aliases::define! {
Eq = ::core::cmp::PartialEq, ::core::cmp::Eq;
Ord = ..Eq, ::core::cmp::PartialOrd, ::core::cmp::Ord;
Copy = ::core::marker::Copy, ::core::clone::Clone;
}
I'm now preparing a v0.5 release, and one of the features it will support is const-deriving traits.
Currently, we have a separate macro for const-deriving traits: #[derive_const]:
#[derive_const(Debug, Ord, Copy)]
struct User;
This has a few usability issues in my mind:
- It is a distinct attribute from
derive. - If you want to const-derive 1 of 8 traits that are being derived, you have to delete the trait in the middle, and add a new attribute
const-deriving traits should be as easy as adding a const annotation next to the trait, directly inside of the derive macro.
In my crate I will support the following syntax for const-deriving:
// Define the aliases
derive_aliases::define! {
Eq = const ::core::cmp::PartialEq, const ::core::cmp::Eq;
Ord = ..Eq, ::core::cmp::PartialOrd, ::core::cmp::Ord;
Copy = ::core::marker::Copy, ::core::clone::Clone;
}
#[derive(const Debug, ..Ord, ..Copy)]
struct User;
Experimenting stuff like that is great in external crates before it can be considered for inclusion in the language.
And the derive-aliases crate is the only one that supports this, likely because (as I later found out) it is extremely hard to create a custom derive that works as you expect. (due to derive helper attributes working with name resolution in a context where we only have access to tokens)
Syntax for const-deriving traits
I've seen several suggestions on this.
The one that first comes to mind:
#[derive(const PartialEq)]
I've also seen this syntax:
#[derive(PartialEq(const))]
With the idea that a derive macro could hypothetically accept an arbitrary tokenstream, much like an attribute macro:
#[derive(PartialEq, Serialize(const, skip_serializing_none, tag = "..."), Deserialize(const, deny_unknown_fields, Debug)]
I am against that idea because this will make it extremely difficult to understand the traits that are being defined, since it won't all be in a single place - there will be a bunch of syntactic noise interspersed with all the derives. It will be abused
I think that the helper attributes that we currently have are perfectly fine:
#[derive(PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)
On that topic of helper attributes, what about if the derive macro just used those?
#[derive(PartialEq, Debug, Eq, Copy, Clone, Ord, PartialOrd)]
#[partial_eq(const)]
#[debug(const)]
#[clone(const)]
// ...
That's a lot of syntax noise!
This is also impossible to add to the language without breaking backwards compatibility, because adding a new partial_eq helper attribute to the PartialEq derive will conflict with BetterPartialEq's partial_eq attribute
We should also have a common interface for const-deriving traits. We dont want to hope that a convention will arise, because it might not. We dont want inconsistency for a fundamental language feature:
#[derive(PartialEq, Debug, Eq, Copy, Clone, Ord, PartialOrd)]
#[partial_eq(impl_const)]
#[debug(const)]
#[clone(const = true)]
// ...
The const keyword should ideally be as close to the derive as possible.
derive(Clone(const)) without allowing extra syntax in those parentheses might be OK, but it will look very out of place with the rest of the language, and derive plus const are both core language features, so they should work as you expect.
So I think the best syntax for const-deriving traits is:
#[derive(const PartialEq)]
How macros know if they are being const-derived
Macros need to somehow find out if they are being const-derived.
One proposal I've seen suggested is that the derive macro can take a 2nd argument:
#[proc_macro_derive(Serialize, attributes(serde)]
fn Serialize(input: TokenStream, metadata: Metadata) -> TokenStream
This metadata object can be opaque and have an fn is_const(self) -> Option<Span> function. We can add more information to this object if we want to in the future.
Notably I think this function should return Option<Span> so that in case a derive macro doesnt support being const-derive, it can create a helpful error message pointing at the const keyword itself.
Should const-derive be the default?
const is not the default anywhere in the language, so in my opinion it would be inconsistent for that to be here.
That is, a trait should always be implemented by a derive macro without const, and only be const-implemented if the const keyword is used
Is it ok for a proc macro to give a
constimpl when called with#[derive]
Note that it's unlikely that a derive macro can always be implemented as const, since usually bounds are added on the item's fields.
For marker traits like Copy, I'm torn..On one hand, it can always be implemented as const so the const keyword would be noise you have to add literally everywhere. On another hand, it would be inconsistent to implement something in const without requiring the const keyword.
Perhaps we can be okay with that, and it could increase the incentive of having something like derive-aliases built-in to the language.