meetings/2020/LDM-2020-10-05.md
record struct primary constructor defaultsdata membersrecord struct primary constructor defaultsWe picked up today where we left off last time, looking at what
primary constructors should generate in record structs. We have 2 general axes to debate: whether we should generate mutable
or immutable members, and whether those members should be properties or fields. All 4 combinations of these options are valid
places that we could land, with various pros and cons, so we started by examing the mutable vs immutable axis. In C# 9, record
primary constructors mean that the properties are generated as immutable, and consistency is a strong argument for preferring
immutable in structs. However, we also have another analogous feature in C#: tuples. We decided on mutability there because it's
more convenient, and struct mutability does not carry the same level of concern as class mutability does. A struct as a
dictionary key does not risk getting lost in the dictionary unless it itself references mutable class state, which is just as
much of a concern for class types as it is for struct types. Even if we had with expressions at the time of tuples, it's
likely that we still would have had the fields be mutable. A number of C# 7 features centered around reducing unnecessary struct
copying, such as readonly members and ref struct improvements, and reducing copies in large structs by with is still a
useful goal. Finally, we have a better story for making a struct fully-readonly with 1 keyword, while we don't have a similar
story for making a struct fully-mutable with a similar gesture.
Next, we examined the question of properties vs fields. We again looked to our previous art in tuples. ValueTuple can be viewed
as an anonymous struct record type: it has value equality and is used as a pure data holder. However, ValueTuple is a type
defined in the framework, and its implementation details are public concern. As a framework-defined pure data holder, it has no
extra behavior to encapsulate. A record struct, on the other hand, is not a public framework type. Much like any other user-
defined class or struct, the implementation details are not public concern, but the concern of the creator. We have real
examples in the framework (such as around the mathematics types) where exposing fields instead of properties was later regretted
because it limits the future flexibility of the type, and we feel the same level of concern applies here.
Primary constructors in record structs will generate mutable properties by default. Like with record classes, users will
be able to provide a definition for the property if they do not like the defaults.
In C# 9, we allow record types to redefine the property generated by a primary constructor parameter, changing the accessibility
or the accessors. However, we did not allow them to change whether the member is a field or property. This is an oversight, and
we should allow changing whether the member is a field or property in C# 10. This will allow overriding of the default decision
in the first section, giving an ability for a "grow-up" story for tuples into named record structs with mutable fields if the
user wishes.
data membersFinally today, we took another look at data members, and what behavior they should have in record structs as opposed to
record classes. We had previously decided that data members should generate public init properties in record types;
therefore, the crucial thing to decide is if data should mean the same thing as record would in that type, or if the data
keyword should be separated from record entirely. In C# today, we have very few keywords that change the code they generate
based on containing type context, and making data be dependent on whether the member is in a struct or class could end up
being quite confusing. On the other hand, if data is "the short way to create a nominal record type", then having different
behavior between positional parameters and data members in a struct could also be confusing.
We did not reach a decision on this today. There are 3 proposals on the table:
data member is public string FirstName { get; set; } in struct types, and public string FirstName { get; init; } in
class types.data member is public string FirstName { get; init; } in all types.data entirely.We'll come back to this in a future LDM.