#[derive(All)]

Would it be a good idea to have a special derive trait that derives every possible trait for the given type? I understand that it wouldn't be great for compile times but it would be pretty cool for early developement.

After the initial developement is done you would just use the implementations that you need. This could be automated too, akin as how one might do use import::*; and then use the r-a action to only import what you're actually using.

For any public type, this would potentially commit to public API that you may not be able to support in the future.

Also, what does "all" mean? Everything in the standard library? Every trait from every dependency you have?

9 Likes

this would potentially commit to public API that you may not be able to support in the future.

Yes, this is why this feature should be emphasized to be developement only, possibliy linted against in library crates.

Also, what does "all" mean? Everything in the standard library? Every trait from every dependency you have?

All deriveable macros that are in scope that are applicable: All of the standard libary ones: Debug, Copy, Clone ... as well as those that you import: use serde::{Serialize, Deserialize};

Of course if one of the fields in your type can't implement one of those traits don't implement it for the type.

I think "all" is just wrong, because it shouldn't be things like derive(DerefPointee) that are in the standard library.

To me, this is more a request for being able to make names for groups of derives. Then you could locally define with "all" means to you in the root of your crate, and use it on everything you define.

14 Likes

This will be hard, because in general the only way to determine whether a derive will succeed is to compile with it. For most derive macros, "does the field implement the trait" is a relevant question, but in the general case, the derive's success conditions can be arbitrarily complex. This is asking for effectively the ability to write

struct Foo {...}

if_this_code_does_not_compile_then_skip_it! {
    impl Serialize for Foo {...}
}

and that’s probably a very difficult thing for the compiler to support at all efficiently (because if it fails, it has to discard existing trait-solving results and try again with the impl removed), and can potentially have a nondeterministic outcome (the output of two derive macros could conflict with each other).

13 Likes

Would it be reasonable to allow function-like macros to appear in attribute position, as long as they expand to zero or more attributes?

macro_rules! derive_value_traits {
    () => { #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] }
}

#[derive_value_traits!()]
struct MyStruct { … }
1 Like

We could also change how derive attribute (or attributes in general) work and allow using funktion-like macros as derive macros?

#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct Foo {
    a: i32,
    b: String,
}

Was syntax sugar for this.

struct Foo {
    a: i32,
    b: String,
}

derive! { struct Foo { a: i32, b: String, }, Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash }

And allow function-like macros to be used derives.

macro_rules! Trivial {
    ( $t:item ) => {
        
        derive! { $t, Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash }
        
        // we can't use because we end up with duplicate definition of foo
        //
        // #[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
        // $t        
    }
}

#[derive(Trivial)]
struct Foo {
    a: i32,
    b: String,
}

One issue is that you can't have overlapping groupings without compiler magic.

(Also being able to quickly make your own derive macro just by using a function-like macro would be great, since I have a lot of code that looks like this:

struct Parens {
    left: OpenParens,
    expr: Expr,
    right: CloseParens,
}

derive_parse_for_struct! { Parens { left, expr, right } }  

There is a crate for this, but lang support would be great.

)

1 Like

Or perhaps… just thinking: what if we could make a subtrait of all the traits to be derived at once and use this subtrait as the one to refer in the derive? We might probably want to distinguish deriveand the deriveAll, which would recursively search for all supertraits to be derived. So, we could have something like:

trait MyTypicalStruct: Debug + Clone + Eq + Ord + Hash {}

#[deriveAll(MyTypicalStruct)]
struct SomeStruct { … } // This gets derived all that Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash

The standard library might eventually provide a trait for the most common case.

1 Like

That will violate the (very important) invariant that derives cannot change the type they are applied onto, only add more code.

No it wont, the derive macro would be given a duplicate of the item definition and the original definition stays where it is. (Just like derives work right now)

// This:

macro_rules! MyTrait {
    ( $meta:meta $vis:vis struct $Name { $($body:tt)* } ) => {
        impl MyTrait for $Name {}
    }
}

#[derive(Debug, MyTrait)]
struct Foo {
    a: i32,
    b: String,
}

// Is transformed into this:

// Original definition stays as-is
struct Foo {
    a: i32,
    b: String,
}

// Derive is given a duplicate of the definition
derive! { struct Foo { a: i32, b: String, }, Debug, MyTrait }

// Which further is transformed into:

struct Foo {
    a: i32,
    b: String,
}

impl Debug for Foo { /* snip */ }

impl MyTrait for Foo {}

That sounds like a recipe for huge problems. What if the duplicate goes out-of-sync with the original definition?

I don't think I understand what you are saying.

How would the duplicate go out-of-sync? Do you mean that the funktion-like derive macro just hallucinates the item definition out of nowhere? How is that any different from a proper derive macro that is just implemented incorrectly.

If macros can change what the derives see by changing the tokens they pass, it can go out of sync. This can even lead to unsoundness with certain derives.

These are already RFCs for something like this Declarative `macro_rules!` derive macros by joshtriplett · Pull Request #3698 · rust-lang/rfcs · GitHub Declarative `macro_rules!` attribute macros by joshtriplett · Pull Request #3697 · rust-lang/rfcs · GitHub

AFAIK this is already the case, and the problem is completly orthogonal to allowing declarative macros to be used as derive/attribute macros.

2 Likes

Not macros that look like derive macros. This is a problem because some derives rely on derives being unable to change the type they are applied to for soundness.

They can always emit an additional type accessible through an associated type on a trait.

I... don't understand what you mean by that?

To be clear, the suggestion is to have the compiler make the macro invocation from the #[derive] attribute, not for the code to duplicate the struct definition.

Now, it could be interesting if

struct S { a: A }
derive!(struct S { b: B }, Trait);

is allowed. Changing the tokens of an item after a derive is expanded is already possible using proc macros "underneath" the #[derive], and derives that rely on that not being the case need to reject the presence of any non-built-in-reserved attributes for that reason. And also rely on the language never un-reserving those names and allowing them to be shadowed by a custom proc macro.

Deriving a gazillion built-in derivable traits bugged me as well, but not any more thanks to derive_everything. I even reexport these trivial proc macros in the prelude of shame, the convenience crate I use for throwaway scripts.

FYI, deriving only derivable traits is infeasible to my knowledge, because it requires reflection, which Rust does not have, as I learned from the blog of someone who refused to speak at RustConf. Getting if a trait is derivable can be known at const time as is done by impls, but not at proc macro time.

Hope some people are satisfied by having to type less.

Maybe duplicated to this thread

It should be better to just have a

#[derive(Copy, Ord)]
struct Foo;

rather than

#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
struct Foo;

Actually we could auto implement higher trait for lower traits. In case we have Ord, we should have Eq and PartialOrd automatically.

1 Like