Overly restrictive trait bounds on derives

Currently the derive macros for std traits place a needless bound on generic arguments in their implementation. For example, deriving Default on this struct will expand to the following

#[derive(Default)]
pub struct Foo<T> {
    value: Option<T>,
}

impl<T: Default> Default for Foo<T> {
    #[inline]
    fn default() -> Foo<T> {
        Foo {
            value: Default::default(),
        }
    }
}

However, this is overly limiting, because Option<T> satisfies default for any T, not just T: Default. This makes code like this not compile, even though it obviously should:

// error[E0277]: the trait bound `T: Default` is not satisfied
fn new_foo<T>() -> Foo<T> {
     Foo::default() 
//   ^^^^^^^^^^^^ the trait `Default` is not implemented for `T`
}

The problem is easy to fix, we want the field types to satisfy the trait, not the generic arguments. Thus, the macro should actually expand to:

impl<T> Default for Foo<T> 
where 
    Option<T>: Default, 
{
    #[inline]
    fn default() -> Foo<T> {
        Foo {
            value: Default::default(),
        }
    }
}

All std derive traits have this problem. I've frequently needed to write my own implementation of trivially derivable types because of this restriction. Even though generic arguments are often field types, this should not be assumed to be always the case, and limiting the implementation like this diminishes the usefulness of the macros.

One issue with this is privacy

#[derive(Default)]
struct PrivateType<T>(T);

#[derive(Default)]
pub struct PublicType<T>(PrivateType<T>);

// expands to

impl<T> Default for PublicType<T> where PrivateType<T>: Default { ... }

You cannot mention private types in the where clauses of public APIs (despite auto-traits essentially doing so...).

And it would be a semver hazard if adding a field to a type changed its trait bounds. (This too is already the case for auto traits, unfortunately.)

While I do get what you mean with this (and if it is in fact so it is a deal breaker), this particular example seems to compile fine in the playground.

Well, to be fair, the trait bounds are already implied, this would just state them in the implementation. It would not work any differently as the way it works now.

Right now, all the trait bounds are derived from always-public information about the type: what its generic parameters are, and not from potentially private information, what the types of its fields are.

2 Likes

Interesting, that changed recently

rustc 1.58.1 (db9d1b20b 2022-01-20)
error[E0446]: private type `PrivateType<T>` in public interface
  --> main.rs:6:1
   |
2  |   struct PrivateType<T>(Option<T>);
   |   --------------------------------- `PrivateType<T>` declared as private
...
6  | / impl<T> Default for PublicType<T>
7  | | where
8  | |     PrivateType<T>: Default,
9  | | {
...  |
12 | |     }
13 | | }
   | |_^ can't leak private type

error: aborting due to previous error

For more information about this error, try `rustc --explain E0446`.


rustc 1.59.0 (9d1b2106e 2022-02-23)
Compiled successfully

This is a very old, well known issue:

3 Likes

The word you're looking for is "perfect derive": https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/

4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.