Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upDefault Struct Field Values #1806
Conversation
KodrAus
changed the title
Default Fields
Default Struct Field Values
Dec 3, 2016
This comment has been minimized.
This comment has been minimized.
|
Interesting, I like it. Not something that really bothered me so far, but I can see the appeal. You mention "constant expressions" a few times. Does mean you want to allow For alternatives, I see there is derive-new crate (using macros 1.1 which is currently unstable), that has an open issue for adding default values with attributes (I assume something like adding |
This comment has been minimized.
This comment has been minimized.
leodasvacas
commented
Dec 3, 2016
|
@killercup |
This comment has been minimized.
This comment has been minimized.
|
@leodasvacas We could make |
This comment has been minimized.
This comment has been minimized.
leodasvacas
commented
Dec 3, 2016
This comment has been minimized.
This comment has been minimized.
|
The goal is to maintain that expectation of cheapness for fields that can be initialised without you knowing. We were talking about allowing |
nrc
added
the
T-lang
label
Dec 5, 2016
nrc
self-assigned this
Dec 5, 2016
This comment has been minimized.
This comment has been minimized.
jFransham
commented
Dec 5, 2016
|
I have a problem with this not playing nice with the trait machinery, if you have a function that takes a generic #[derive(Default)]
struct Foo {
a: i32,
b: i32,
}
Foo {
a: 10,
..Default::default(),
}is slightly unergonomic, but it's explicit and allows a user wondering where values come from to follow the initialize_b_only({
a: 10
})(syntax is imaginary). |
This comment was marked as resolved.
This comment was marked as resolved.
|
#[derive(Eq, PartialEq)]
struct S {
a: u8 = 1,
b: u8 = 1,
}
let s = /* ... */;
if s == S{} {
/* ... */
}
EDIT: never mind, this is not worse than the current situation, see #1806 (comment) |
This comment has been minimized.
This comment has been minimized.
|
@jFransham I think this plays just as nicely with traits as Maybe this is a different case because you're actively giving each field a default value and the result is that it can be initialised with no outside data. So it basically is I think an important distinction between what we've got now and what this RFC proposes is that it lets structs define their own default for types on a per-field basis. So instead of having one value for Having less boilerplate around builders would definitely make them more useful here, but are still more effort. |
This comment was marked as resolved.
This comment was marked as resolved.
|
@dtolnay Following that parsing ambiguity around lools like you're all over it? Are you concerned there are cases being missed? I'd think it would be better to nail that behaviour down deterministically and avoid the problem altogether rather than making it less common and hoping people don't do it. That's said with no appreciation of how much work might be involved though :) |
petrochenkov
reviewed
Dec 5, 2016
| } | ||
| ``` | ||
|
|
||
| We can add a new field `b` to this `struct` with a default value, and the calling code doesn't change: |
This comment has been minimized.
This comment has been minimized.
petrochenkov
Dec 5, 2016
Contributor
Unless .. is made optional in patterns new field b can't be added here because it would break patterns Foo { a, b }.
This comment has been minimized.
This comment has been minimized.
petrochenkov
Dec 5, 2016
Contributor
However, if structure has a special private field to force .. in patterns - Foo { a, b, .. /*__hidden: ()*/ }, then it can be extended.
This comment has been minimized.
This comment has been minimized.
KodrAus
Dec 5, 2016
Author
Contributor
My immediate thought is that .. in patterns would also have to be optional then for consistency. I'm cautious about that though because it might increase the scope of this thing too much.
The other option is make .. required for structs with defaults, or stop pretending it's always backwards compatible.
This comment has been minimized.
This comment has been minimized.
leodasvacas
Dec 5, 2016
It is unfortunate for partial construction and partial destructoring to have an inconsistent syntax. If making .. optional in patterns is an acceptable way forward, then I would suggest for this RFC to mention that because of patterns you need a private field for backwards compatibility, and leave changing pattern syntax to a future RFC.
petrochenkov
reviewed
Dec 5, 2016
|
|
||
| ## Explicit syntax for opting into field defaults | ||
|
|
||
| Field defaults could require callers to use an opt-in syntax like `..`. This would make it clearer to callers that additional code could be run on struct initialisation, weakening arguments against more powerful default expressions. However it would prevent field default from being used to maintain backwards compatibility, and reduce overall ergonomics. If it's unclear whether or not a particular caller will use a default field value then its addition can't be treated as a non-breaking change. |
This comment has been minimized.
This comment has been minimized.
petrochenkov
Dec 5, 2016
Contributor
However it would prevent field default from being used to maintain backwards compatibility
There's no backward compatibility already (https://github.com/rust-lang/rfcs/pull/1806/files#r90957633), so mirroring pattern syntax or omitting .. is mostly a stylistic choice (unless, again, .. is made optional in patterns).
This comment was marked as resolved.
This comment was marked as resolved.
EDIT: never mind, this is not worse than the current situation, see #1806 (comment) |
This comment has been minimized.
This comment has been minimized.
|
Not sure how I feel about this RFC, but syntactically I would strongly prefer some indication of the missing fields in the constructor, possibly just That is: struct Foo {
bar: u32 = 0,
baz: u32 = 1,
}
let foo = Foo { bar: 7, .. };
let foo = Foo { .. }; |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats I'm not against requiring |
This comment has been minimized.
This comment has been minimized.
|
I support adding Right now, X is usually an implementation of |
This comment has been minimized.
This comment has been minimized.
|
Well that would also save having to change the destructuring pattern so ok I'm sold. |
This comment has been minimized.
This comment has been minimized.
|
Yea, the goal would be to make it clear to people what is happening in the code they're reading, since they aren't necessarily familiar with the definition of the structs involved. I think this is a case where "explicit is better than implicit" is a helpful maxim. |
This comment has been minimized.
This comment has been minimized.
|
One thing that I haven't yet seen brought up is the interaction with (potential, admittedly) function argument defaults, along with defaults in other places. Do we want to settle on |
This comment has been minimized.
This comment has been minimized.
|
@Mark-Simulacrum |
This comment has been minimized.
This comment has been minimized.
|
Now something to figure out is how to explain field defaults with |
This comment has been minimized.
This comment has been minimized.
keeperofdakeys
commented
Dec 7, 2016
|
Requiring |
This comment has been minimized.
This comment has been minimized.
crumblingstatue
commented
Dec 7, 2016
•
I would rather like to see work on named and optional arguments, which would allow constructors to solve this problem, and make other APIs more ergonomic as well. If Rust had to pick between optional fields for structs, and optional/named function arguments, I would pick the latter, as it allows more ergonomic APIs, e.g. |
This comment has been minimized.
This comment has been minimized.
|
@crumblingstatue I don't see why Rust couldn't have both default struct fields and default function arguments. |
This comment has been minimized.
This comment has been minimized.
leodasvacas
commented
Dec 7, 2016
•
|
As @KodrAus already mentioned, making this explicit with @crumblingstatue This is a much smaller adition to the language than named or optional parameters and yet solves some use cases that would need both of those features, so it's a win for those who want such features even if it dosen't solve all use cases. Edit: Change of wording. |
This comment has been minimized.
This comment has been minimized.
|
@leodasvacas I'm thinking a pragmatic way forward would be to propose this with an explicit syntax so it's in line with the current state of the world. The biggest benefit of implicit syntax kind of falls over with patterns right now, so they seem like a coupled issue. If callers want expliciteness it's going to come at the cost of incompatibility because everything is surfaced to them. A future RFC could propose allowing implicit The similarity but difference to FRU is the problem I'm thinking about at the moment. |
This comment has been minimized.
This comment has been minimized.
|
Closing as postponed -- thanks @KodrAus! As mentioned above, this is definitely an area where we anticipate doing work in the future, but there are a number of thorny, interlocking design considerations. |
aturon
closed this
Sep 28, 2017
Centril
added a commit
to Centril/rfcs
that referenced
this pull request
Mar 5, 2018
Centril
referenced this pull request
Oct 7, 2018
Closed
Default trait: some variables don't have defaults #569
This comment has been minimized.
This comment has been minimized.
derekdreery
commented
Oct 7, 2018
|
.. a year later: I'm a big fan of this RFC. Builder pattern can be verbose, and you are relying on the compiler to make it efficient. |
Centril
added
the
postponed
label
Oct 7, 2018
This comment has been minimized.
This comment has been minimized.
|
I like this RFC; I like everything about it.
All in all, there's absolutely nothing I would want to change in this proposal (except to update the wording to reflect 2018); it is perfect in my view. Given that this RFC was postponed, I'd like to revisit it now. ...@KodrAus are you interested in pursuing this proposal further? If so, I'll reopen the PR. (some of the text needs to be updated to reflect changes in |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Nov 4, 2018
|
I oppose this RFC, for the following reasons:
Overall, this RFC, while seems somewhat useful, doesn't seem to provide enough and strong enough advantages to warrant a language change, while it is also problematic (in terms of being future-proof) in the face of structs with private fields. I don't find the motivation very convincing either. I understand that the proposed syntax is somewhat more convenient to use than writing a builder pattern or more than one constructor, but these mainly (with one exception) concern the person implementing an API, not those consuming it. The exception is of course the "config struct in constructor parameter" pattern, |
This comment has been minimized.
This comment has been minimized.
This makes the
It couldn't because making it a library would be a breaking change; at most it can be a sort of quasi library proc macro that the compiler provides by default. In any case, if we implement this RFC, nothing changes. It can still be a library and the crate
"Magic" is in the eye of the beholder. When pattern matching a tuple with I also feel that there's a sort of duality between pattern matching and ignoring the extra fields and constructing and providing extra fields. I don't think the usual "pattern matching follows construction" is violated here. There are even nice laws here: let x = Foo { alpha, beta, .. };
let Foo { alpha, beta, .. } = x;
let y = Foo { alpha, beta, .. };
assert_eq!(x, y);
I think there is something mostly "unimportant" and "ignorable" going on, otherwise I wouldn't use default values on fields. I also don't think I think that if defaults were being used when writing
Sure, sometimes they do; however, even with pub struct Unique<T: ?Sized> {
pointer: NonZero<*const T>,
_marker: PhantomData<T> = PhantomData,
}
Unique { .. } // Error! `pointer` is missing.
Unique { pointer: <value>, .. } // Still, error! `pointer` isn't visible...
It is always possible to add a private field typed at some unit type; now the type cannot be constructed with
I think the language (+ team) should be concerned with making libraries (and libraries within applications) convenient to write. |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Nov 4, 2018
Of course not now – but it could have been, that's what I meant.
I see that, but that's mostly ignoring the semantics. (Also just because it's mathematically appealing, it can have issues in practice, which I think this construct definitely has, as I mentioned before.)
Sorry, then I think we are just disagreeing here.
So could this RFC with two of the proposed semantics. Only one of them prevents side effects; the others (allowing arbitrary expressions or only
Yes (but I doubt many users would know this or do it by default out of caution…), and then this part of the RFC completely loses its use anyway. By the way, this would now be yet another thing that programmers implementing an API would need to remember – "always add a mostly meaningless ZST private field by default, if you want to prevent construction via a literal". I think this is the wrong way around, as it makes the potentially dangerous situation the default, requiring one to opt out of it (and not even in a way that might be obvious to people).
Definitely. I still don't think this particular RFC is justified. It's not like structs are hard or inconvenient to use today. |
This comment has been minimized.
This comment has been minimized.
But what value does the argument have if it's not actionable?
Yeah sure, just because there are mathematical laws doesn't mean it cannot have other problems, but then those need to be demonstrated (and I don't think sufficient problems have been...).
I think we should discuss what the RFC actually proposes, and it proposes that the default value of a
I disagree that what you need to remember is "always add a mostly meaningless ZST"... I think almost always the other fields that don't have defaults are private (and then you wouldn't The language is set up in such a way that the default thing is to respect the principle of least privilege since fields are private by default. If your API is dangerous in some way such as if it involves struct Id<A, B> {
prf: PhantomData<(fn(A) -> A, fn(B) -> B)> = PhantomData,
}
impl<S: ?Sized, T: ?Sized> Id<S, T> {
pub fn cast(self, value: S) -> T where S: Sized, T: Sized {
unsafe {
let cast_value = mem::transmute_copy(&value);
mem::forget(value);
cast_value
}
}
}
let refl: Id<u8, Box<u8>> = Id { .. }; // And we have bricked the type system.but in this case you used |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Nov 4, 2018
The point was that
This is exactly what I'm trying to say (assuming by "it" you are also referring to |
This comment has been minimized.
This comment has been minimized.
Right, I think that's a worthwhile goal. But do you agree that this RFC doesn't change anything in this respect -- so the principle is retained? With respect to how the principle is even enhanced, consider for example #[derive(Debug, Arbitrary, Deserialize)]
struct Foo {
// The proptest_derive macro will see this
// and fix the value of `bar` to always be 42.
// Serde will see this and assume that if no value is
// provided then `42` will be used on deserialization.
bar: u8 = 42,
// other fields...
} |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Nov 4, 2018
I was asking if it does - and if it doesn't, I do agree it is retained. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Nov 4, 2018
•
|
I'll tentatively disagree with the constructor If I understand, the I'd agree with @H2CO3 opposition to this feature if I also think
And this RFC's ergonomics can be recaptured with polymorphic constants:
We could then simple write |
This comment has been minimized.
This comment has been minimized.
crumblingstatue
commented
Nov 4, 2018
|
My main problem with this feature is that it's a less generic solution than what could be achieved with constructor functions if Rust had named and optional args. Quoting from the motivation:
This problem would be nullified if Rust had optional and named arguments. Reasons why it's less generic than constructor functions
I don't fundamentally oppose this feature, but I believe optional and default arguments would cover all the motivation for this feature, without the constant-only drawback, while also fulfilling other motivations. As a closing statement, I want to acknowledge that optional/default arguments are indeed controversial, and they may take years to land, if ever. I just wanted to make sure this information is out there for people to consider. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Nov 4, 2018
|
I believe structural records #2584 along with this feature provide precisely named and optional args since you could write:
|
This comment has been minimized.
This comment has been minimized.
I don't mean "dual" in the literal category theoretical sense; but it does give such an "aura". This is sort of vague, but I hope you understand kinda what I mean. As for using
This is true because of
I think code writers benefit from this as well; it gives me great comfort when writing code to know that side effects are not being done behind my back.
Possibly, but the motivation isn't solely as an alternative to named and optional arguments (which are quite controversial on their own because of a host of problems particularly with the former...).
But you would get other problems, for example, some variants on named arguments make function names part of public APIs and most variants of named arguments don't interact well with the trait system. Most floated solutions to optional arguments don't mesh well with how defaults work elsewhere in the language.
The drawbacks are outdated. T-libs could make
Yes, however, optional and named arguments does not enhance
A constructor function can make use of the default values and not make use of others... You can still check the invariants that need to be checked if there are any. I don't think default values need to solve all problems. ;)
I have demonstrated, in this, and other comments, that optional + named arguments do not cover all the motivations.
Very fair! :) |
This comment has been minimized.
This comment has been minimized.
crumblingstatue
commented
Nov 4, 2018
•
Making sure these two features work together to allow this use case is definitely worth looking into. It would not cover all the use cases of optional and named args ( Uhh, never mind the part about runtime parameters. Those only apply to the implicit defaults, the user could still explicitly provide runtime-dependent values. Actually, I really like this idea. Even for those use cases that default struct field values don't cover, you could write your constructor function using structural records and default values combined. If these features end up working together, they might be the answer to optional and default args. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Nov 4, 2018
•
|
I'm endorsing the feature in what I wrote @Centril : You'd want a Also, easily readable code aids in correctness, while ergonomics favorable to writing code without being favorable to reading code tends to harm correctness. I'm mostly saying |
This comment has been minimized.
This comment has been minimized.
I view this feature as a foundational step towards named and optional arguments. Named arguments should have struct-like syntax and desugar into structs of some kind. With such a feature, then default fields gives you optional arguments 'for free'. |
This comment has been minimized.
This comment has been minimized.
jplatte
commented
Nov 5, 2018
|
This seems like a pretty useful feature, but I don't understand the rationale for allowing the construction of structs that contain invisible fields with it, especially given that FRU isn't supported for structs with invisible fields. |
This comment has been minimized.
This comment has been minimized.
derekdreery
commented
Nov 5, 2018
|
@jplatte Maybe this feature could be combined with a non-exhaustive flag, so #[non_exhaustive]
struct Options {
first: bool,
}would not allow instantiating the struct, similar to if you did struct Options {
first: bool,
#[doc(hidden)]
__private: ()
}and #[non_exhaustive]
enum Example {
Variant1,
Variant2,
}is equivalent to enum Example {
Variant1,
Variant2,
#[doc(hidden)]
__NonExhaustive,
}This has been discussed before I think - there was talk of syntax like enum Example {
Variant1,
..
}but I can't remember the details of the conversation. |
KodrAus commentedDec 2, 2016
•
edited
Rendered.