
# C# Language Design Meeting for March 30, 2020

## Agenda

Records

1. Builders vs init-only

2. Possible conflicts with discriminated unions

3. Value equality

## Discussion

### Builders vs. init-only

We discussed more of the tradeoffs of using builders vs using an "init-only" feature for records,
and looked into requirements for other languages, including VB and F#. Based on our current
designs, the work needed to consume the new features for "init-only" seem fairly small.
Recognizing the `modreq` and allowing access to init-only members, and calling any necessary
"validator" on construction, are pretty simple features for VB. VB already has the syntactic
requirements for the feature (object initializers), and we'd like to keep it possible for VB to
consume major changes in the API surface, if not write those new features. F# is undergoing
active development and changes are certainly viable there. Because the scope of changes is more
open-ended in F#, it's possible it could feature more implementation work.

Notably, most of the features associated with "init-only" and "validators" do not require a new
runtime, only a new compiler version. The new compiler version is necessary to recognize safety
rules (validators must always be called after constructors, if they exist), but they don't use
any new features in the CLR. The only feature potentially requiring runtime features is
overriding a record with another record, which could potentially require covariant returns.

The remaining differences seem to come down to whether you can "see" the whole object during
initial construction (as opposed to validation). If you can see the whole object immediately,
that makes writing a `With` method that avoids a copy if all the values are identical very
straightforward. However, this could be done for "init-only" as well, by moving this semantic to
the `with` expression, optionally comparing the arguments to the `With` expression with the
values on the receiver object and avoiding calling With if they are identical. Therefore we don't
think we're actually ruling anything out by going down the "init-only" route.

There are advantages in going down the "init-only" route instead. The performance for structs
is probably better and more optimizable, and the IL pattern seems clearer and less bloated.

**Conclusion**

We like the "init-only" IL better. Given the path forward for other languages and compatibility
with many runtimes, we think it's a better future as an IL pattern.

### Conflicts with discriminated unions

There was a general question if these decisions could impact a future discriminated unions feature.
We don't have a lot in mind, but if we do end up building a discriminated union made of the records
feature, there is one component we may want to reserve. Discriminated unions often have a set of simple
data members. For instance, if we wrote a Color enum as a discriminated union, it could look like.

```C#
enum class Color
{
    Red,
    Green,
    Blue
}
```

If we reduce these to records, it may look like:

```C#
abstract class Color
{
    public class Red() : Color;
    public class Green() : Color;
    public class Blue() : Color;
}
```

The problem is: since those classes are effectively singletons, we'd like for the instances to be
cached by the compiler, to avoid allocations. However, we don't currently have a syntax in C# that
means "singleton." Scala uses the "empty record" syntax to mean singleton. We need to decide if we'd
like to reserve that syntax ourselves, or find some other solution.

### Value equality

We've decided that we want value equality by default for records. We need to settle on what that
means. The primary proposal on the table is shallow-field-equality, namely comparing all fields
in the type via EqualityComparer. This would match the semantic we have decided on for "Withers,"
where the shallow state of the object is copied, similar to MemberwiseClone.

There's a large segment of the LDM that thinks doing anything except for field comparison is
problematic because it introduces far too many customization points for the record feature.
Almost all custom equality would have to deal with sequence equality and string equality, which
are already very different mechanisms. There's a proposal that we could provide further
customization via source generators, which could allow almost any customization.

A follow-up to that is: why have value equality by default at all? Why not use source generators
for all value equality? One problem with that is that we want the simplest records to be very
short. The other problem is that we effectively have to pick an equality (C# will inherit one if
you don't). We previously decided that value equality is a non-controversial default -- if it's
wrong it's probably not worse than when reference equality is wrong, and it's often better.
Struct-like field-based equality is a simple and familiar version of value equality.

We also need to decide on value-equality as a separable feature for regular classes. Some people
like the idea of a separate feature and would be fine even with the constrained version that only
compares fields. Others don't think this feature meets the hurdles for a new language feature. If
we don't have customization options, it may be rarely used, and it seems possible that a source
generator version could be the much more popular version. It's also worth noting that, as a
separable feature, we don't need to add separable equality now.

**Conclusion**

No separable value equality for non-records, right now. Default record equality is defined as
field-based shallow equality.