Add notes for 7/1/2020

This commit is contained in:
Fredric Silberberg 2020-07-01 16:27:26 -07:00
parent 1bb454804d
commit 464e8d16e3
No known key found for this signature in database
GPG key ID: BB6144C8A0CEC8EE
2 changed files with 131 additions and 1 deletions

View file

@ -0,0 +1,129 @@
# C# Language Design Meeting for July 1st, 2020
## Agenda
1. [Non-defaultable struct types and parameterless struct constructors](#Non-defaultable-struct-types-and-parameterless-struct-constructors)
2. [Confirming unspeakable `Clone` method implications](#Confirming-unspeakable-Clone-method-implications)
## Quote of the Day
"I did not say implications, I said machinations [pronounced as in British English]. I used a big word."
"You mispronounced machinations [pronounced as in American English], which is why I'm just ignoring you."
## Discussion
### Non-defaultable struct types and parameterless struct constructors
Proposal: https://github.com/dotnet/csharplang/issues/99#issuecomment-601792573
#### Parameterless struct constructors
We discussed both proposals for allowing default struct constructors and for having a feature to allow differentiating between
`struct` types that have a valid `default` value, and types that do not.
First, we looked at parameterless constructors. Today, `struct`s cannot define their own custom parameterless constructors, and
a previous attempt to ship this feature in C# 6 failed due to a framework bug that we could not fix.
```cs
public struct S
{
public readonly int Field = 1; // Field is set by the default constructor
}
public void M<T>()
{
var s = (S)Activator.CreateInstance(typeof(T));
Console.WriteLine(s.Field);
}
```
In .NET Framework and .NET Core prior to 2.0, a call to `M` will print `0`. However, .NET Core fixed this API in 2.0 to correctly
call the parameterless constructor of a struct if one is present, and since we now tie language version to the target platform
there will be no supported scenario with this bug.
While there was general support for this scenario in the LDM, we spent most of the time on the second part of the proposal and
did not come away with a conclusion for parameterless constructors. We will need to revisit this in context of a reworked defaultable
types proposal and make a yes/no conclusion on this feature.
#### Non-defaultable struct types
The crux of this proposal is that we would extend the tracking we introduced in C# 8 with nullable reference types, and extend it
to value types that opt-in, holes and all. From a type theory perspective, the idea is that by applying a specific attribute, a
struct type can indicate that `default` and `new()` are _not_ the same value in the domain of its type. In fact, if the struct does
not provide a parameterless constructor, `new()` wouldn't be in the domain of the struct at all. This attribute would further opt the
struct's `default` value into participating in "invalid" scenarios in the same way that `null` is part of the domain of a reference
type, but is considered invalid for accessing members directly on that instance. This played well with our previous design of `T??`,
if we were to allow the `??` moniker on types constrained to `struct` as well as unconstrained type parameters. However, as `??`
has been removed from C# 9 due to syntactic ambiguities (notes [here](LDM-2020-06-17.md#T??)), that part of the proposal will have
to be reworked. Not having `??` makes the feature much harder to explain to users, and we'll run into issues with representation in
non-generic scenarios.
One thing that is clear from discussion is that non-defaultable struct types will need to have some standardized form of checking
whether they are the default instance. `ImmutableArray<T>`, for example, has an `IsDefault` property, as do most of the existing
struct types in Roslyn that cannot be used when `default`. We would want to be able to recognize this pattern in nullable analysis,
just like we do today with the `is null` pattern. Since the attribute and pattern would be new, we could declare it to be whatever
we desire, and the libraries will standardize around that if they want to participate.
Generics also present an interesting challenge for non-defaultable value types. Today, the `struct` constraint implies new:
```cs
public void M<T>() where T : struct
{
var y = new T(); // Perfectly valid
}
```
If we were to enable non-defaultable struct types, this would change: `new()` is not necessarily valid on all struct types because
non-defaultable struct types have explicitly opted to separate `default` and `new()` in their domain, and might not have provided
a value for `new()`, meaning that it would return `default`. From an emit perspective, this is further complicated: for the above
code, the C# compiler _already_ emits a `new()` constraint. C# code cannot actually specify both the `struct` constraint and the
`new()` constraint at the same time today, but in order to actually emit the combination of these constraints for this feature
we would have to introduce a new annotation on the type parameter to describe that it is required to have a parameterless `new()`
that provides a valid instance of the type.
`ref` fields in structs also came up in discussion. This is a feature that we've been asked for by the runtime team and a few other
performance-focussed areas, but is very hard to represent in C# because it would require a hard guarantee that a struct with a
`ref` field is truly never defaulted, by anything. `ref`s do not have a "default", so a struct that contained on in a field would
need to not be possible to default in any fashion. This proposal could overlap with that feature: the guarantees provided here are
no stronger than the guarantees given with nullable reference types, which is to say easy to break: arrays of these structs would
still be filled with `default` on creation, for example, even if the type wasn't annotated with a `??` or hypothetical other sigil.
We need to be sure that, if that's the case and we do want to add `ref` fields, we're comfortable having both a "soft" and "hard"
defaultness guarantee in the language.
Finally, there was some recognition and discussion around how this issue is very similar to another long standing request from
libraries like Unity: custom nullability. The idea is that with C#, among the entire value domain of a type we recognize and have
built language support for one _particular_ invalid value: `null`. However, this isn't the only invalid value that a value domain
may have. Unity objects have backing C++ instances, and they override the `==` operator to allow comparison with `null` to also
return true if the backing field has not yet been instantiated. While the C# object itself is not `null` in this case, it _is_
invalid, and should be treated as such. However, this doesn't play well with other operators in C#, such as `?.`, `??`, and
`is null`. These all special-case a particular invalid value, `null`, and don't play well with other invalid values, leading
libraries like Unity to encourage users to write code that does not take advantage of modern idiomatic C# features. This issue is
very similar to the non-defaultable structs issue: we'd like to recognize a particular value in the domain of a struct type as
invalid. It might be better to implement this as a general invalid value pattern that any type, struct or class, can opt into.
#### Conclusion
For both of these issues, we need to take more time and rethink them again, especially in light of the removal of `??`, which
the non-defaultable struct type proposal relied on heavily. A small group will explore the space more, particularly the more
general invalid object pattern, and come back with a rethought proposal. The guiding principle that this group should keep in
mind from the current proposal is "Users should be able to change something from a class to a struct for performance without
significant redesign due to having to handle an invalid `default` struct value."
### Confirming unspeakable `Clone` method implications
Before we ship unspeakable `Clone` methods for `with` expressions, we wanted to make sure that we've worked through the
consequences of doing so, and are sure that the language will be able to continue to evolve without breaking scenarios that
we are enabling with this feature. In particular, in the face of a general factory pattern that users can use to extend record
types, or even potentially expand what is today a record type into a full blown type without breaking their customers, we
might need to emit both the unspeakable `Clone` method and a factory method in the future. A guiding principle for record design
has been that whether something is a record is an implementation detail. Therefore whatever future method we add that will allow
a regular class type to participate in a `with` expression will likely have to emit this method as well.
We also considered whether we should take any measures right now to try and keep our design space open in records for adding a
user-overridable `Clone` method. We could try emitting the method now, and modreq it so that it cannot be directly called from
C# code, or we could just block users from creating a `Clone` method in a record entirely.
#### Conclusion
We're fine with the unspeakable name being a feature of records forever going forward. We will also reserve `Clone` as a member
name in records to ensure that our future selves will be able to design in this space.

View file

@ -21,11 +21,12 @@
## Jul 6, 2020
- [Required properties](https://github.com/dotnet/csharplang/issues/3630) (Fred)
## Jul 1, 2020
- [Non-defaultable struct types](https://github.com/dotnet/csharplang/issues/99#issuecomment-601792573) (Sam, Chuck)
- Confirm unspeakable `Clone` method and long-term implications (Jared/Julien)
- [Required properties](https://github.com/dotnet/csharplang/issues/3630) (Fred)
## Jun 29, 2020