Comparing to patterns, ..
seems like it has the appropriate duality to me.
If I understand correctly, default
as a contextual keyword in place of *
here would not work, because Foo { ..default }
is already valid Rust code.
This reminds me of the conversation in Short enum variant syntax in some cases, where I tossed together this macro:
macro_rules! s {
( $($i:ident : $e:expr),* ) => {
{
let mut temp = Default::default();
if false {
temp // this tricks inference into working
} else {
$(
temp.$i = $e;
)*
temp
}
}
}
}
With that, I think your example would be
let _: Foo = s! {
bar: [s! {
qux: [s! {
zap: [s! {
val: "val",
}],
}],
}],
}
Having some sort of language feature (or even just a macro) like that could be cool, since it sounds like your situation is also one where it'd be nice to not have to type the name of the type.
It also makes me once again want the different version of FRU that can work with non_exhaustive
types or those with private fields, so more things can use this kind of API if they want.
You could already simplify this to
use Default::default;
Foo {
bar: [Bar {
qux: [Qux {
zap: [Zap {
val: "val",
..default()
}],
..default()
}],
..default()
}],
..default()
}
which is quite close to your proposed default
keyword.
Edit: Actually I just tested this and it turns out that items in traits are not importable.
You can accomplish the same shortening by writing:
fn default<T: Default>() -> T {Default::default()}
I’ve noticed the calls for "syntactic sugar" or need for macros in Rust just because of its wordiness / explicitness. Default::default()
is one example. Another one is all those helper macros for implementing traits in crates like derive_more or smart_default. Coming from Haskell I mostly see artificial and unnecessary problems here.
In Haskell, functions from type classes (i.e. traits) are never namespaced under the name of the typeclass. So when importing the Default
class, you can immediately just write def
where a Rustacean needs to write Default::default()
. Thats 3
vs 18
characters. The artificial problem here is that what is unavoidable in Haskell (non-namespaced functions from traits) is impossible in Rust, without resorting to writing a new wrapper function.
At least the wrapper function is possible so you can
fn def<T: Default>() -> T {Default::default()}
and your problem goes away
Foo {
bar: [Bar {
qux: [Qux {
zap: [Zap {
val: "val",
..def()
}],
..def()
}],
..def()
}],
..def()
}
I would be quite happy, if the wraper function could be replaced by a
use Default::default as def;
Also having Default::default
pre-imported (as default
) in the prelude is kind-of a must.
I won’t go too much into my off-topic point about implementing traits but I see a big problem in this need of re-stating the entire function/method signature of every function/method in every trait impl
. I don’t think these helper macro crates were half as popular if implementing traits was more fun and less boilerplate. (And, by the way, I don’t think that the explicit signatures are the only problem that makes implementing traits tedious. There’s lots of room for improvement, including things Haskell doesn’t have (yet) either.)
..*
would be ambiguous with ..*some_ptr_or_ref
.
Nightly currently has a free function default
, so you could just write ..default()
rather than ..Default::default()
. That would be a substantial improvement, without adding syntax.
Interesting! Just the other day I was thinking that struct update syntax is a feature that I use or encounter in code I read exceedingly rare, and was wondering whether this feature pulls its weight at all...
Having a free function default()
in the prelude would be great. I currently use <_>::default()
fairly often to avoid imports and type repetition.
I think a huge part of this is that it only works on types where all the fields are accessible (all public and not non-exhaustive, or inside the same crate). That basically keeps libraries from using it for "options structs" unless they're willing to do a major version bump for every field addition. Thus they're forced into more-boilerplate builder APIs even if they don't need the extra capabilities those can offer.
I regularly use C# APIs that have a ton of these (random example) and Rust could do an even better job at this, thanks to inference and being able to pass them as &FooOptions
. (In C# with shared mutable ownership it's never clear who might be modifying something, especially in convenience wrappers.)
It also breaks in less-obvious cases, like types that implement Drop
and have non-Copy
fields. Personally, rough edges like this make me less likely to use update syntax as part of my habitual Rust toolbox.
I'm currently using a library where all the methods look like async fn do_a_thing(&self, req: DoAThingRequest) -> ...
, so all my calls to it look like thing_doer.do_a_thing(DoAThing { field, field2, ..Default::default() }).compat().await?
.
That's a large amount of boilerplate. default()
will help. I also hope I can use _ { field, field2, ..default() }
someday.
Some of the concerns raised seem to be around the minor sharp edges around the existing feature, which I understand. Another one that hasn't been raised is it is impossible to have partial applicability: having a struct where some fields are optional with default values, but some that are mandatory. Because of the way the feature works it wouldn't be possible today to write something like Foo { mandatory_field, ..default() }
because Default
can't really be implemented for it.
But these are in my eyes current limitations that we could work around somehow, and if we did we could provide a very idiomatic and less verbose alternative to the builder pattern.
I'm in favor of having a Struct { field: value, .. }
syntax. But I think it would be better served by being a first-class "default the rest of the fields" feature rather than just sugar for standard FRU, so that it's usable for more cases.
That reminds me of yet another previous conversation about allowing customized field defaults. One could imagine struct Foo { x: i32 = 4 }
such that Foo { .. }
gave Foo { x: 4 }
. So we have to be careful when picking exactly which semantic is desired here.
EDIT: That again also gets to non_exhaustive
too -- one would want a way to say "this is non_exhaustive
but I promise I'll only add things with defaults" or something.
If all the fields in the struct are Default
or have a default value, you could easily write a proc_macro to provide that feature. Adding it to the language could have the benefit of giving the compiler enough context for the "some fields have defaults and some not so you have a set of mandatory fields for construction" case.
I think that what I would like to see is the composition of this feature, the proposed feature and anonymous literals/structural records to allow us to write foo(_ { x = 1, .. })
when given
fn foo(_ { x: i32, y: i32 = 42 })
or
struct Bar {
x: i32,
y: i32 = 42,
}
fn foo(Bar { x, y }: Bar) {}
But of course, the individual features should stand on their own merits.
I also hope I can use _ { field, field2, ..default() } someday.
I hope someday there will be default values for struct fields and it will be possible to just write: _ { field, field2 }
What about something like;
...!
-
..etc
or...etc
...
(I'm new here, but I'm trying to bring in some new perspective to this idea.)
The ..
syntax would be an unprecedentedly implicit way of calling a function (or even multiple functions if we consider the "default the rest of the fields" feature). Initializing a struct's field with a default value is often incorrect, so I think this action should be more explicit and noticeable.
If default values were limited to constants it wouldn't be that bad would it?
If anything more complex is required a crate could provide a constructor and/or Default::default