meetings/2023/LDM-2023-07-31.md
readonlyhttps://github.com/dotnet/csharplang/issues/2691
https://github.com/dotnet/csharplang/issues/188
https://github.com/dotnet/csharplang/discussions/7377
https://github.com/dotnet/csharplang/blob/bc2c69e6f2bd28373e341effaa1182ebcb7ea72c/proposals/readonly-parameters.md
We picked up again from last Wednesday. To restate the options we'd started with in that meeting:
readonly keyword. This keyword may come with C# 12, but might also be delayed until C# 13. There are open questions as to
what this keyword would mean: would it follow field readonly, where the parameter capture would actually be mutable during initialization, or not?
But by default, primary constructor parameters would always be mutable unless otherwise specified.record types today), but for classes and readonly structs, they would be readonly after construction.readonly.We'd eliminated option 1 and 4 during that meeting (4 very decisively, 1 not very decisively), leaving options 2 and 3. One important clarification we made in this
meeting, though, was that 1 and 2 are not actually mutually exclusive. Many members of the LDM would be fine shipping C# 12 with 1, and then doing a readonly keyword
in C# 13. So we're really debating between 2 and 3, but not necessarily committing to doing 2 before C# 12 ships.
The main thrust of the debate between 2 and 3 is the presence of opinion in mainline C# code that is different than C# opinion elsewhere in mainline code. Parameters
are always mutable by default in C#, as are fields; it's only in automatically generated record class that we default to readonly (or more specifically, init).
So no matter where we end up on this issue, we have a decoder ring for users: either we need to explain why we chose to make primary constructor parameters readonly,
or why we chose to make records different than the rest of the language. This distinction revealed an important point in the debate, however: are we bringing primary
constructors, as created by records, back to the rest of the language, or are we bringing existing creation patterns forward with primary constructors, and viewing
records as a step further?
One thing LDM was immediately unanimous on was not wanting a mut or mutable keyword, even if we were to go with option 3. We don't have such a concept elsewhere
in the language, and needing to add it here is immediately concerning. We also investigated whether we can confidently promote primary constructor parameters to full
type members in a simple fashion: could we, for example, have private automatically turn into a private readonly field, and public automatically turn into a
public { get; init; } property? We're not sure about this either: private seems fine, but the public version feels iffy. Is init, as in records, the right
default? And if it's not, is that yet another area where we'll need to explain the differences between records and non-records?
In terms of evolution, there also a number of LDM members who are concerned about the slippery slope that is evolution in the first place. How far should we plan on
taking this? We start with simple member declarations, but then those declarations might need validation, default values, differing public and private names, the
ability to specify partial when we eventually allow partial properties, etc. At what point do we say that we've gone far enough and that any further member-like
things need to be actual type members? It's clear that will be a question for our future selves, and that it's unclear where we'll land when we get there.
We finally turned back to the question of what direction of evolution we are intending for primary constructor parameters. Another way of looking at this question is:
if we had flipped the design order, delivering primary constructor parameters before records, instead of the other way around, would we be doing anything differently?
We don't think so: when we look at how we want to explain the language, we think that there's a core set of rules around mutation that apply everywhere in the language,
except for records. records exist to allow C# users to work with data more easily, and they contain a number of opinions that make this easier, from value-based
equality to readonlyness for reference types. We think that this is the C# we want to explain; the alternative would be explaining that primary constructors differ
from the rest of C# by bringing in opinions from record types. It would potentially be easier to explain to existing users, but harder to explain the language
philosophy as a whole. We therefore conclude that primary constructor parameters will ship in C# 12 as is. We will prioritize work on a readonly modifier for primary
constructor parameters, including figuring out whether this modifier will mean readonly like it does for fields, where it is mutable during initialization, or if
will mean fully readonly, with no ability to mutate at all. This may not make it into C# 12, but we will target early previews of C# 13.
Primary constructor parameters will ship as mutable-by-default, and we will investigate the readonly parameter modifier shortly.