# C# Language Design Meeting for April 28th, 2021

## Agenda

1. [Open questions in record and parameterless structs](#open-questions-in-record-and-parameterless-structs)
2. [Improved interpolated strings](#improved-interpolated-strings)

## Quote of the Day

- "I was kinda hoping you'd fight me on this"

## Discussion

### Open questions in record and parameterless structs

https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/csharp-10.0/parameterless-struct-constructors.md  
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md

#### Initializers with explicit constructors

The first thing we need to resolve today is a question around our handling of field initializers, both in the presence of an
explicitly-declared constructor(s), and when no constructors are present. There's two parts to this question:

1. Should we synthesize a constructor to run the field initializer? and
2. If we don't, should we warn the user that their field initializer won't be run?

We feel that a simple rule here would be to mirror the observed behavior with reference types: if there is no explicit constructor
for a struct type, we will synthesize that constructor. If that constructor happens to be empty (as it would be today in a
constructorless struct type because field initializers aren't yet supported) we optimize that constructor away. If a constructor
is explicitly declared, we will not synthesize any constructor for the type, and field initializers will not be run by `new S()`
unless the parameterless constructor is also explicitly declared. This does have a potential pit of failure where users would
expect the parameterless constructor to run the field initializers, but synthesizing a parameterless constructor would have bad
knock-on effects for record structs with a primary constructor: what would the parameterless constructor do there? It doesn't
have anything it can call the primary constructor with, and would result in confusing semantics. To address this potential issue,
we think there is room for a warning wave dedicated to using `new S()`, when `S` does not have a parameterless constructor. There
will be some holes in this, particularly around generics: `struct` today implies `new()`, and we're concerned about how breaking
the change would be if we tried to make the warning apply to everywhere that a parameterless struct was substituted for a type
parameter constrained to `struct`. We would also have to take another language feature to enable `where T : struct, new()`, which
isn't allowed today. If there is future appetite for introducing another warning wave to cover the generic hole, we can look at
it at that point.

##### Conclusion

We will only synthesize a constructor when the user does not explicitly declare one. We will consider a warning wave when using
`new` on a struct that does not have a parameterless constructor and also has an explicit constructor with parameters.

#### Record struct primary constructors

Next, we looked at how the parameterless struct constructor feature will interact with record primary constructors. The rule we
decided for the first question ends up making the decisions very simple here:

1. Parameterless primary constructors are allowed on struct types.
2. The rules for whether an explicit constructor needs to call the primary constructor are the same as in record class types. This
applies to an explicit primary constructor too, just like in record class types.

##### Conclusion

Use the above rules.

#### Generating the Equals method with `in`

We looked at a potential optimization around the `Equals` method, where we could generate it with an `in` parameter, instead of
with a by-value parameter. This could help scenarios with large struct types get better codegen. However, when we would want to do
this is a very complicated heuristic. For structs smaller than machine pointer size, it is usually faster to pass by value. This
gets even more complex when considering types that are passed in non-standard registers, such as vectorized code. We don't think
there's a generalized way to do this heuristic. Instead, we just need to make sure that the user can perform this optimization, if
they want to.

##### Conclusion

We won't attempt to be smart here. Users can provider their more customized equality implementation if they so choose.

#### `readonly` Equality and GetHashCode

Finally in record structs, we looked at automatically marking the `Equals` and `GetHashCode` methods as `readonly`, if the all the
fields they use for the calculation are also all `readonly`. While this would be technically feasible, we're not sure what the
scenario for this is, beyond just marking the entire struct `readonly`. At that point, every method would be readonly, including
the ones we synthesize.

##### Conclusion

We don't do anything smart here. Users can just mark the struct as `readonly`.

### Improved interpolated strings

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

We're getting close the end of open questions in this proposal. We looked at 2 today:

#### Confirming `InterpolatedStringBuilderAttribute`

An internal discussion on simplifying the conversion rules resulted in a proposal that we only look for the presence of a specific
attribute on a type to determine if there exists a conversion from an interpolated string literal to the type. This simplification
results in much cleaner semantics: the presence of various methods on the builder type no longer plays into whether the conversion
exists, only whether conversion is valid. This will help users get understandable errors that don't silently fall back to
string-based overloads when a builder doesn't have the right set of `Append` methods to lower the interpolated string.

We also looked at whether we should control the final codegen based on a property on the attribute: we initially proposed conditional
evaluation could be controlled by the attribute. This would potentially let the compiler change the behavior of when expressions in
interpolation holes are evaluated: up front, or in line the `Append` calls. However, the logic for determining the codegen is not
as simple as one property: the `Append` calls can potentially return `bool`s to stop evaluation, and the `Create` method can
potentially `out` a parameter to control this as well. There are valid scenarios for all 4 possibilities here, so a switch that only
allows either all conditional or no conditional isn't a good option. It's also complex because this would be a library author making
a lowering decision for user code. We also note that, if we decide that this is important at a later date, we can extend the pattern
then.

##### Conclusion

Using the attribute is accepted. The `Append` and `Create` method signatures will drive the lowering process.

#### Specific extensions for structured logging

Finally today, we looked at a question around including specific syntax to support structured logging. [Message templates](https://messagetemplates.org/)
are a well-known structured logging format that most of the biggest .NET logging frameworks support, and as we want these libraries
to consider using the improved interpolated strings feature, we looked to see if we can include specific syntax to help encourage
this. After some initial discussion, our general sentiment is that this feels too narrow. An example syntax we considered is
`$"{myExpression#structureName,alignment:format}"`, and while this would work for the scenario, it wouldn't be a meaningful improvement
over simply using a tuple expression: `$"{("structureName", myExpression),alignment:format}"`. It is possible to construct interpolated
string builders that only accept tuples of `string` and `T` in their interpolation holes, and with the other adjustments we made today
there should be good diagnostics for such cases. Further restrictions can be imposed via analyzers, as is possible today. While a more
general string templating system could be interesting, we think that this is a bit too narrow of a focus for a language feature today.

##### Conclusion

We won't pursue specific structured syntax at this time.
