meetings/2019/LDM-2019-10-21.md
We'd like to make some progress on records, in both design and implementation. One development in our view of records is to consider them not as a feature in and of itself, but a shorthand for a set of features aimed at working with a collection of data. Specifically, we have a proposal to consider a guiding principle: a record should only be capable of generating code that the user could write themselves.
To that end, we can look at the set of features we're considering incorporating into records, and differentiate between them based on the uncertainty of their design. The features which may have generated behavior are:
Automatic structural equality
With-ers (per type?)
Object initializers for readonly (per member)
Primary constructors + deconstruction
Some features, like with-ers and object initializers, have many open issues. Some features, like generation of structural equality, have widely agreed upon semantics, once the set of members that are part of the structure are decided.
It's proposed that we take a subset of the features, namely primary constructors and structural equality, and consider them to have designs ready for implementation. Primary constructors still have open semantic questions, especially around their meaning outside of a records, but all of the proposed records specify some type of primary constructor with very similar semantics. An example of the common semantics would be the following,
data class Person(string Name, DateTime Birthday);
which would generate two record members, Name and Birthday, structural equality on those two
members, and a corresponding constructor/deconstructor pair. The data modifier, as well as the
primary constructor, are not necessarily final syntax, but the semantic decisions downstream of
this stand-in don't heavily depend on the specific form of syntax chosen and could be easily
changed.
Conclusion
This looks like a reasonable starting point. Records and the components should definitely be designed together, to ensure they fit together well, but it's worth highlighting the more settled pieces as we go.
A brief discussion of the init-only fields feature. One clarification: the init-only properties would be allowed to have setters. Those setters can be executed in the object initializer, or in the constructor of the object. There is a proposal to allow them also to be set in the constructors of base types, which has no opposition at the moment.
There's also a problem with "required" init-only members as currently proposed. The current design specifies that "required" init-only members (members without an initializer) would produce an error on the construction side if the member is not initialized in an object initializer.
For example,
class Person
{
public initonly string Name { get; }
}
void M()
{
var person1 = new Person() { Name = "person1" }; // OK
var person2 = new Person(); // Error, Name was not initialized
}
Unfortunately, the proposed emit strategy for initonly members would only provide
an error in source, not in metadata. If a consumer compiled against a previous
implementation of a type, and a required initonly member was added, no error would
be provided if the consumer were not recompiled. Instead, the member would silently
be set to the default value.
A simple alternative is to drop "required" initonly members entirely. Setting an initonly
member would be optional, as it is for public readonly members today, and if it is not set it
would retain its default value. The recommendation for adding required members would be the same
as it is today: use a constructor parameter, which cannot be skipped.
We could also attempt to repair the situation by lowering the required initonly
members into constructor parameters, but this seems undesirable for many reasons,
including that it's complex to implement, it risks creating collisions with
constructor overloads with the same type, it creates a mismatch in the number of
parameters between source and IL, etc. It does not seem worth going down this path.
Static lambdas were elided mostly for time reasons and we're interested in bringing
them back. The main hang-up on the syntax is adding modifiers before the lambda
syntax, but we already crossed that bridge with async.
The only semantics question is whether or not a static lambda guarantees that the
emitted method will be static in metadata. This is true for static local functions,
but there are some important scenarios, like extern local functions, which depend
on that and could not be implemented for lambdas. In addition, there have been
numerous performance improvements in lambdas and local functions that have taken
advantage of their unspecified metadata characteristics, and at least one significant
performance optimization for lambdas would be lost by requiring them to be static.
Conclusion
The static keyword will be allowed on lambdas and it will have the same capturing rules as
static local functions. It will not require that the lambda be emitted as static.