Skip to content

Latest commit

 

History

History
93 lines (69 loc) · 6.85 KB

LDM-2022-02-14.md

File metadata and controls

93 lines (69 loc) · 6.85 KB

C# Language Design Meeting for February 14th, 2022

Agenda

  1. Definite assignment in structs
  2. 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

#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, 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).
  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

#5740
#4665

Following the last 2 meetings 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.