Alternative derive macro for `Default`, based on `new`

When defining a struct or enum, sometimes derive(Default) is not usable or not as smart as one would like. E.g:

#[derive(Default)]
pub struct Foo<T>(Vec<T>);

In this example, Foo<T> will only implement Default if T does, which is a shame because Vec<T> implements defaults regardless (as it produces an empty vec).

In such cases, I end up defining a new() method and implementing Default::default() manually, one of these implementations relying on the other, e.g.

pub struct Foo<T>(Vec<T>);
impl<T> Foo<T> {
    pub fn new() -> Self {
        Foo(vec![])
    }
}
impl<T> Default for Foo<T> {
    fn default() -> Self {
        Self::new()
    }
}

That's a little too much boilerplate to my taste. It would be nice to have a derive macro that generated the impl<T> Default ... block whenever an new() method with no parameter existed. I could therefore write:

#[derive(DefaultNew)]
pub struct Foo<T>(Vec<T>);
impl<T> Foo<T> {
    pub fn new() -> Self {
        Foo(vec![])
    }
}

Does this make sense to anyone other than me? Is there already a crate providing such a macro?

PS: arguably, my example above could be handled by derive(Default) and maybe it will eventually. But I believe there will always be cases where the implementation needs to be done manually, and therefore where the DefaultNew derive macro could be useful.

If you are using rust-analyzer, there's a quick fix does exactly this. (Invoke quick fix when text cursor is on new.)

And no, derive(Default) cannot handle this. Derive macro could only process the block it is ascribed to, it cannot see whether there is a fn new() -> Self.

This is the first example in derivative.

The limitation of this is that it doesn't (can't) know what the bounds on new are. The derivative derive I believe takes the simple approach of just not bounding the Default impl; derive(Default) takes the conservative[1] route of bounding all generic types with Default. But if you have a partial bound (e.g. impl<T: Default, U> Default for MyType<T, U>), there's no way around writing out that bound.

.... well, here's one way it could be shortened utilizing spitballed, imaginary nonfeatures:

struct MyType<T, U> {
    data: T,
    void: PhantomData<U>,
}

impl<T: Default, U> MyType<T, U> {
    pub fn new() -> Self {
        Self { data: default(), void: default() }
    }

    impl Default for Self {
        use Self::new as default;
    }
}

but just using the "generate Default from new" intent of rust-analyzer in your IDE is honestly fine and nonproblematic. We're not trying to golf code to the fewest characters; we're trying to write code where the intent and functionality are clear.


  1. The alternative is called "perfect derive" where you bound the field types instead of the generic types, e.g. Vec<T>: Default. This is bounding the impl on what it actually requires, but it's a privacy/stability problem, since it requires exposing the private details (field types) of your type, and adding fields (traditionally considered nonbreaking) now impacts when derived impls are applicable. ↩ī¸Ž

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.