The feature of being able to skip initializing some fields in a constructor expression is quite useful, I believe. The field: Type = value
syntax the seems quite appropriate for such a language feature, and once this feature exists, it could be a useful idea to be able to use this syntax also for some macros.
Under this view, I would agree that without the default field values feature that allows you to do let ascribe = ExprType { expr: make_expr(), ty, .. };
style initialization, it's quite reasonable to seriously question the introduction of any new syntax.
Now assuming that we do get default field values, it seems like a reasonable logical thing to do to separate that proposal from the usage of the syntax in macros, and only do the latter as a subsequent proposal/feature. However, that's not possible if we want behavior such as
#[derive(Default)]
struct Foo { field: u32 = 42 }
to use the value 42
for field
in the Default
implementation, instead of the value u32::default()
. Since we don't want to introduce breaking changes, the only way to introduce field default values, without also immediately deciding how the interaction with derive(Default)
is supposed to work, is to have derive(Default)
not support field: Type = value
syntax at all in the meantime.
There'd also be no way to use the #[derive(Default)]
in a way that ignores the = value
, even though this syntax's main purpose is only about the meaning of writing Foo { .. }
.
For Default
this probably isn't a huge problem; who wouldn't want Foo { .. }
and Foo::default()
to be the same value? And in the odd case where you don't want it, you can always write your own impl Default for Foo
.
(From here on, this is not really "just" an answer to @H2CO3 anymore, but brings up new, fairly unrelated points...)
I'm bringing up these points also because they apply to other macros, too. Except none of those have the privilege that Default
has: the = value
support for Default
macro can be introduced together with the default field values feature, so breaking changes can be avoided. The same is not a possibility for other macros. There's two ways to solve the problem of breakage:
- the macro still requires an attribute on a field if that field's default value in
= value
syntax should be used for the macro
- the macro just doesn't support the new
= value
syntax in a struct/enum it's applied to at all (users will need to update to a new version of the crate providing the macro that does support it)
however, unlike with Default
, third-party macro authors don't actually have any way to decide between these two alternatives; either their macro already accepts (and ignores) the new fields, or it doesn't. Only in the latter case can they actually use the = value
expression on a field in a meaningful way without also introducing some new syntax for it. (Well, I guess at least in this case they do have the ability to decide something after all.)
Now, many macros use syn
, and most macros will not want to care about any new field: Type = value
syntax, so it would make a lot of sense if syn
just starts accepting this syntax in a non-breaking manner. However, this means that macros that do want to add some way to incorporate the new syntax will fall into the first category above, so they need to add this support it in a non-breaking manner.
serde
and structopt
both use syn
. So probably for serde
, something like
#[derive(Deserialize)
struct Foo {
#[serde(default)
field: u32 = 42,
}
would still use a default of 0
, and you'd need new syntax, for example something like
#[derive(Deserialize)
struct Foo {
#[serde(default = _)
field: u32 = 42,
}
to use the provided default value. Arguably, the first case is quite confusing; to avoid confusion, serde
should probably (somehow? maybe we'd be getting proc_macro_diagnostics
stable befor this feathre..) add a warning for this case, requiring the user to rewrite the thing either to
#[serde(default = "Default::default()")
or
#[serde(default = _)
for improved clarity.
Similar considerations apply to structopt
, though they'd have (unlike serde
) the realistic option of releasing a breaking change in a new major version, so that ... oh wait I just realized that structopt
only supports string literals as default values, because those are passed to a clap
API that always expects a &str
. That means that this feature is completely useless for structopt
because the types don't match; unless they'd want to start supporting turning certain literals (like numbers) into string literals (but this still doesn't scale at all to any other more complex types). And in any case, it would be unusable for arg: String
fields, which are probably fairly common.
While considering syn
, I also noticed that it seems like a pretty hard thing to add these default value declaration to its API in a non-breaking manner.1 Maybe it's even impossible? In which case the discussion above and its conclusions (like that like that serde
has to add new syntax such as e.g. serde(default = _)
) becomes irrelevant, and OTOH, any worst-case, any macro that doesn't even care about the new = value
stuff would presumably need to update to a new 2.0
version of syn?
I'd be interested in hearing what @dtolnay thinks about this.
1The Field
: struct doesn't really leave room for the = value
part in a struct
/enum
item. The Foo { .. }
expressions seem less problematic as currently, ExprStruct
has the already-existing ..rest
thing in two separate Option<_>
s already. (I don't actually know why it's like that.)