-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Proposal]: Auto-default structs #5737
Comments
Aren't we going to have the same questions for classes and semi-auto props and fields WRT nullability analysis? |
Yes, I wrote a bit about nullable analysis of semi-auto properties but decided to remove it from here and raise it separately, since it seems like it doesn't really depend on the containing type kind. |
Regarding the conclusion from LDM:
We have followed up. We didn't find any new reasons, beyond what we've already considered, that the 'out this' behavior was chosen originally for struct constructors. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
We recently explored the interaction between this feature and dotnet/roslyn#30194. An example of the interaction: // ClassLibrary1.csproj
public struct S1
{
private object x;
}
// ConsoleApp1.csproj (references ClassLibrary1.csproj)
public struct S2
{
public S1 s1;
// assume all scenarios use the same compiler here.
// C# 8: no diagnostics. implicit 's1 = default'.
// C# 9: LangVersion warning diagnostic. implicit 's1 = default'.
// C# 11: disabled-by-default warning diagnostic. implicit 's1 = default'.
public S2(bool unused) { }
} Our conclusions are:
|
It looks like we are preserving the ability of a team to have different members on different compiler versions avoid breaking each other. So, I think this is a good place to land on this. |
This feature will be available in preview in VS 17.3. |
This is cool. I never really understood why structs had the explicit assignment requirement in the first place, though I may have read about it sometime in the past and just forgot. |
@HopefulFrog Because: var x = new S();
x.F = 10;
x = new S();
Console.WriteLine(x.F); // will print 10 if F isn't initialized in struct constructor.
struct S
{
public S() { }
public int F;
} Now, with this proposal implemented, the compiler will add |
I'm not sure I understand your example. Are you saying that with structs, a |
By default, that's not correct. The compiler now explicitly assign fields to their default values so that you see the behavior you want. Meaning the compiler adds some code so that To clarify more, for the example above, here is the IL of the struct constructor per SharpLab:
Note that if the compiler didn't produce the instructions to assign 0 to |
Yeah, structs are weird. The constructor takes an 'out' ref to a variable as an argument and the compiler is free to use refs to uninitialized variables as arguments or to reuse variables that contained something else before as arguments. |
I had no idea. So, what happens in the constructor if it's not part of an assignment, like if your statement is just |
The compiler creates a temporary variable. |
What is the order of initialization of those fields? |
@paulozemek The default assignment occurs before your
(Emphasis mine) As an aside:
In cases like this you can use |
Thanks! It would be nice if the compiler identified that those bytes were already set and didn't even try to initialize them first... but there is a valid solution, so this is great. |
I don't really see much of a reason or benefit in this. I have been saved by the kind of error this seems to want to remove a considerable number of times in the past, when I actually forgot to assign a member of a struct. Why have this when the go-to solution in such a case has always been public struct S
{
public int X { get => field; set => field = value; }
public S() : this()
{
}
} Sure, you might have a parameterless constructor, but then a syntax like |
This sounds great! And would eliminate one of the big annoying differences between defining a type as a struct versus a class. |
This proposal is raised as a possible mitigation for usability issues found in #5552 and #5635, as well as addressing #5563 (all fields must be definitely assigned, but
field
is not accessible within the constructor).Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/auto-default-structs.md
Since C# 1.0, struct constructors have been required to definitely assign
this
as if it were anout
parameter.This presents issues when setters are manually defined on semi-auto properties, since the compiler can't treat assignment of the property as equivalent to assignment of the backing field.
We assume that introducing finer-grained restrictions for setters, such as a scheme where the setter doesn't take
ref this
but rather takesout field
as a parameter, is going to be too niche and incomplete for some use cases.One fundamental tension we are struggling with is that when struct properties have manually implemented setters, users often have to do some form of "repetition" of either repeatedly assigning or repeating their logic:
Previous discussion
A small group has looked at this issue and considered a few possible solutions:
this = default
when semi-auto properties have manually implemented setters. We agree this is the wrong solution since it blows away values set in field initializers.We've also received feedback from users who want to, for example, include a few field initializers in structs without having to explicitly assign everything. We can solve this issue as well as the "semi-auto property with manually implemented setter" issue at the same time.
Adjusting definite assignment
Instead of performing a definite assignment analysis to give errors for unassigned fields on
this
, we do it to determine which fields need to be initialized implicitly. Such initialization is inserted at the beginning of the constructor.In examples (4) and (5), the resulting codegen sometimes has "double assignments" of fields. This is generally fine, but for users who are concerned with such double assignments, we can emit what used to be definite assignment error diagnostics as disabled-by-default warning diagnostics.
Users who set the severity of this diagnostic to "error" will opt in to the pre-C# 11 behavior. Such users are essentially "shut out" of semi-auto properties with manually implemented setters.
At first glance, this feels like a "hole" in the feature, but it's actually the right thing to do. By enabling the diagnostic, the user is telling us that they don't want the compiler to implicitly initialize their fields in the constructor. There's no way to avoid the implicit initialization here, so the solution for them is to use a different way of initializing the field than a manually implemented setter, such as manually declaring the field and assigning it, or by including a field initializer.
Currently, the JIT does not eliminate dead stores through refs, which means that these implicit initializations do have a real cost. But that might be fixable. dotnet/runtime#13727
It's worth noting that initializing individual fields instead of the entire instance is really just an optimization. The compiler should probably be free to implement whatever heuristic it wants, as long as it meets the invariant that fields which are not definitely assigned at all return points or before any non-field member access of 'this' are implicitly initialized.
For example, if a struct has 100 fields, and just one of them is explicitly initialized, it might make more sense to do an
initobj
on the entire thing, than to implicitly emitinitobj
for the 99 other fields. However, an implementation which implicitly emitsinitobj
for the 99 other fields would still be valid.Changes to language specification
We adjust the following section of the standard:
https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11712-this-access
We adjust this language to read:
If the constructor declaration has no constructor initializer, the
this
variable behaves similarly to anout
parameter of the struct type, except that it is not an error when the definite assignment requirements (§9.4.1) are not met. Instead, we introduce the following behaviors:this
variable itself does not meet the requirements, then all unassigned instance variables withinthis
at all points where requirements are violated are implicitly initialized to the default value (§9.3) in an initialization phase before any other code in the constructor runs.this
does not meet the requirements, or any instance variable at any level of nesting within v does not meet the requirements, then v is implicitly initialized to the default value in an initialization phase before any other code in the constructor runs.Design meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md#definite-assignment-in-structs
The text was updated successfully, but these errors were encountered: