# C# Language Design Meeting for February 14th, 2022

## Agenda

1. [Definite assignment in structs](#definite-assignment-in-structs)
2. [Checked operators](#checked-operators)

## Quote of the Day

- "Do we have any historical records on how the early design meetings handled bio breaks?"

## Discussion

### Definite assignment in structs

https://github.com/dotnet/csharplang/issues/5737

As part of designing the semantics of the `field` keyword in value types, we have to decide what the impact to definite assignment is.
In a [previous meeting](LDM-2022-01-12.md#definite-assignment-for-struct-types), we looked at some possible solutions, and a small group
met to discuss a couple of possible solutions:

1. Require `this = default;` in the constructor of the struct. We quickly discarded this as unusable: we just added field initializers,
and this would completely blow those initializers away.
2. Automagically initialize all backing fields to default. We like this idea, but are concerned it's very broad, and would lead to double
initialization
3. Provide a way to assign the backing field of an auto or semi-auto property and require users to assign it. We thought this was tedious,
and could potentially lead to user confusion.

Of the 3 options, we liked 2 the best. However, futher thought lead to the idea being debated today, which is to repurpose the existing
definite assignment for struct parameters to be used for determining what fields need to be assigned by default.

In order to think about this, we wanted to look back at the original design for structs, and examine why C# didn't take this route in the
beginning. While there are currently no members of the LDT who were on the design team at that time, we have a decent understanding of the
reasoning here:

1. C# originally had "making porting from C really easy" as a top-of-mind issue. In C/C++, value types are often initialized by assigning
to all the components of the value directly, rather than by calling any named constructor. In order to make this work with the concept of
definite assignment in C#, that meant that the struct must be able to be considered definitely assigned after all components have been
initialized (this was the source of the bug in the native compiler that resulted in [CS8882](https://github.com/dotnet/roslyn/issues/30194)).
2. For consistency with local definite assignment for value types, the constructor of a value type has similar restrictions. For `this` to
be accessible in the constructor, it must fulfill similar restrictions to `this` as a local.

However, this "similar restrictions" reason has a hole in it: regular partial properties already throw a wrench in the mix by creating an
anonymous field that can't be seen by anyone. Further, since private fields aren't visible outside the struct, there was already a hole in
this logic from the start. We think that, given these points, using definite assignment for this purpose with the option to activate warnings
to go back to the original behavior in C# 10 and prior is a fine solution. We will follow up with emeritus members of the LDT who were
present when the original restrictions were made, but barring any large revelations we like this approach.

#### Conclusion

Proposal is accepted, contingent on following up with emeritus LDT members on original reasoning around struct definite assignment.

### Checked operators

https://github.com/dotnet/csharplang/pull/5740  
https://github.com/dotnet/csharplang/issues/4665

Following the last [2](LDM-2022-02-07.md) [meetings](LDM-2022-02-09.md#checked-user-defined-operators-contd) on checked operators, we
reviewed the detailed update on checked operators. In general, this is attempting to resolve the hierarchy of importance for operator
resolution, where the elements are:

1. Nearness of the operator - how far up the type hierarchy is the operator? IE, defined on the type being used, or on its base, or
that type's base, etc.
2. Betterness - this is standard overload resolution, ie which types are better suited.
3. Checkness - this is a new axis for operator resolution: whether we're in a checked context or not.

For existing resolution, the order is nearness, then betterness. Containing types are searched _until_ an applicable operator is found,
and then all operators from that type are added to the candidate type list and no further types are searched. At that point, the candidates
are narrowed via betterness rules. Importantly, this means that, even if an operator further up the tree is "better" (types are a more exact
match), it will never be found because nearness is more important than betterness. In the previous meetings, we decided that, for the new
axiom of checkness, checkness is less important than nearness, but more important than betterness, and for the most part the new rules
reflect this.

However, we are still a bit conflicted on this ranking of axioms. Part of this conflict is in how the operators are defined; we don't have
"checked" and "unchecked" operator flavors, we have "checked" and "default" operator flavors. This means that, in derived type that provides
a single default operator, that default operator will always be preferred, even if the base type defines a checked operator. This implies that
checkness is less important than nearness; in the linked PR, this is example 4. _However_, the rules are also written that, in unchecked
contexts, no checked operators are even considered. If nearness was truly more important than checkedness, then if we encounter a type with
only `checked` operators, we should stop lookup, and then fail to resolve an operator because that type doesn't define an appropriate default
operator. However, that's not the proposal states: instead it only looks for default operators, completely ignoring all checked operators (shown
in example 5). This difference has a few members of the LDT uncomfortable, and we think we need more time to mull the rules over.

We also acknowledge that, no matter whether checkness or nearness is ultimately more important, the BCL team is going to need to have careful
guidance on breaking changes with introducing new checked versions of operators, as a new checked operator will always be considered better
for a checked operation than all default operators on a given type. This is also another source of tension around our concern here: if nearness
is more important than checkedness, then any subtype that introduces a new checked operator must introduce default operators as well, so that
they don't break any users of the operators from a base type. The same is true in reverse for checkness being better than nearness: if checkness
is more important, than any subtype that introduces a new default operator must also introduce a checked version of that operator, to ensure that
the base type's checked operator is not erroneously preferred over the subtype's.

#### Conclusion

No conclusion was reached today. We need to think more about this scenario.
