[Pre-RFC] Automatic initializers


#1

Summary

If a generic constructor function appears in a struct expansion/update, it should be used to initialize the remaining variables using type inference.

Motivation

The classic generic in Option may not be Default and you have a lot of fields (e.g. in a generic builder)

#[derive(Default)]
struct Builder<T> {
a: Option<A>, 
b: Option<B>,
t: Option<T>,
...
}

This (likely) doesn’t do what you want, because the derived impl requires T: Default, and instead you need:

impl<T> Default for Builder<T> {
fn default() -> Builder<T> {
a: Option::default(),
b: Option::default(),
t: Option::default(),
...
}
}

This gets unwieldy quickly.

Details

A generic constructor function is any function of the form:

fn foo<T>() -> T {
    ...
}

That is, a function with (at least) one generic argument, no args, and one return value of the expected type.

This definition is quite relaxed, and allows things like:

struct MyBuilder {
x: Option<Foo>,
y: Option<Bar>,
z: Baz
}

impl MyBuilder {
fn new() -> MyBuilder {
    MyBuilder { z: Baz, .. Option::default }
}
}

Where Option::default takes one generic parameter T, and outputs an Option<T>. This compiles, because all fields (except z) are Option.

This syntax works because fn aren’t destructurable, and so the update syntax makes no sense for fn.

Alternatives

Do nothing.

Unresolved questions

Should it be possible to use multiple automatic initializers, if their bounds don’t overlap?


I’m supposed to write more here but idk what I’m missing.


#2

… because the derived impl requires T: Default? That’s not always a problem, but if this is what you mean, please say so specifically.


#3

I don’t see the unwieldiness in the example? Sure you need to provide a value for each field explicitly, but this aids understanding, so I don’t see the issue?


#5

This can be solved by pulling out the non-generic parts into a non-generic struct. Assuming the number of other fields is really that high, the cost should amortize over them:

#[derive(Default)]
struct BuilderOpts {
   a: Option<A>,
   b: Option<B>,
}

struct Builder<T> {
   opts: BuilderOpts,
   target: Option<T>,
}

impl<T> Default for Builder<T> {
    fn default() -> Self {
        Builder {
            opts: Default::default(),
            target: None,
        }
    }
}

#6

So much for some of the ideas thrown around in the other thread I guess…