-
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: non-defaultable structs #2019
Comments
Will the following warn? Wrapper<int> val1 = Enumerable.Empty<Wrapper<int>>().FirstOrDefault();
var val2 = Enumerable.Empty<Wrapper<int>>().FirstOrDefault(); And will |
I want |
@quinmars Essentially, there may be another operator which indicates the default value of a type. So T? in an unconstrained generic would mean T? if T is a non-nullable reference type, T if it as a nullable reference type, T if it is a normal struct, and T? if it is a non-defaultable struct. |
@ufcpp |
If/when we get discriminated unions (DUs), a means of marking a type (whether class or struct) as non-defaultable becomes pretty much a must. If I declare: public enum struct Maybe<T>
{
Some(T value),
None()
} then I'll want the compiler to ensure I cannot write: Maybe<int> maybe = default;
… as I'm leaving the struct in its default state, which makes no sense. That As we are getting flow control for nullable reference types, I'm hoping that control flow logic can be repurposed to not allow DU's to ever be default. If such "non-defaultableness" can be achieved for DU's, then it seems only a small step to extend this to any type. Whether this happens via a keyword or attribute seems less important to me. It's the ability to mark a type of non-defaultable and have flow control enforce it that seems the important part. And by using flow control, we avoid the need for a CLR change too, which seems a big added bonus. |
@DavidArno |
Hmm, I was going to argue that NRE's only act on the instance, but I'd like a way of saying that the class itself should never be allowed to be
then But I now think there's a flaw in my thinking. Even a The nagging doubt I now have though is what happens with a class-based DU? |
@DavidArno
|
To explain why I say If I declare my DU as a class: public enum class Maybe<T>
{
Some(T value),
None()
} Then, when I pattern match on it: Console.WriteLine(maybe switch
{
Some(var value) => $"Value is {value}",
None() => "No value"
}); then I don't want a warning that I've not covered the null-state and so the switch isn't exhaustive. But there again, maybe the fact that |
Exactly my thoughts |
@YairHalberstadt, I think I need to think things through before posting as that's twice now you've spotted the flaw in my argument and commented before I've had time to work out the flaw for myself 😆 |
I agree. Maybe the solution should be allowing to define in valuetype class the default static value you want as a readonly. Then, if not assigned in the struct definition, the built-in default(T) would be used, instead (as usual, so as not to introduce breaking-changes). This way, default(T) and new T(), could be different. |
I can see this argument for a |
@jnm2, surely |
@Ultrahead |
They already are. The latter will invoke a parameterless constructor if one exists. C# currently doesn't let you define a parameterless constructor on a struct, but that may change. |
@DavidArno certainly. I see DUs as an extension of enums. I don't have a problem with the default value of |
What should be the default value of public enum struct Result<T>
{
Ok(T result);
Error(Exception ex);
} |
Ah well, maybe that gets to the heart of it. Likewise, why is So I'd be unhappy with this same "implementation detail" artefact of default values then being applied to DU's, which are truly nominal data in most cases, just because everything else to date also has a default state. |
I'd suggest that there really isn't a good solution to the "default value" problem, certainly not without dragging CLR changes into the mix which I imagine would spin out for a bit. So in my opinion it's worth having the conversation as how to design DUs in such a way to try to avoid the problems that will result in a zeroed-out default. From a language perspective it's probably reasonable to treat DUs like enums, where you can have a degree of confidence that the DU will be one of the enum values but where there is no guarantee. |
As @YairHalberstadt has pointed out, when v8 lands, reference types will no longer have a default state, unless you explicitly mark them as nullable. This instantly changes the argument. No longer must everything have a default. Extending this "no default state" to opt-in structs makes a lot of sense. And it neatly addresses the only real opposition to having C# directly support Having landed in this position (if we did), then having the norm for DU's be "non defaultable", unless explicitly overridden, makes complete sense to me. |
Something like that would probably work fine, require a value on initialization. There's still no guarantee, and you still have the gaping holes with array initialization and generics, but hopefully that would be sufficient. |
I've long valued the specific convenience of defaulting value types: I don't have to initialize fields to |
@jnm2 |
@YairHalberstadt Exactly, and this is particularly painful in the case of |
I guess @YairHalberstadt that you answer it your-self ...
|
I disagree. This is exactly what i want as it means a default immutable array behaves the same as a default normal array. |
Except it doesn't, because it can't. Sure, |
@Ultrahead I would never want default(T) to be a non zeroed out struct and new T() to be a different non zeroed out struct! |
Wouldn't changing what Frankly it is kind've silly to have |
@Willard720 |
This is our plan. See dotnet/roslyn#30597 |
instead of |
@MkazemAkhgary |
!default
…________________________________
De: Yair Halberstadt <[email protected]>
Enviado: Monday, November 26, 2018 3:04:16 PM
Para: dotnet/csharplang
Cc: Pedro Güida; Mention
Asunto: Re: [dotnet/csharplang] Proposal: non-defaultable structs (#2019)
@MkazemAkhgary<https://github.com/MkazemAkhgary>
I think that's a good idea if you can write a default constructor on a normal struct.
However if you can only write a default constructor on a nodefault struct, it should be part of the language, and so should require a keyword not an attribute.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#2019 (comment)>, or mute the thread<https://github.com/notifications/unsubscribe-auth/ALhvAIAJ37eHsnooxIjuJk5AGSKEDeJpks5uzC0ggaJpZM4Ysonr>.
|
You'd have to prevent The reason WrapperStruct can't be used as a generic type parameter is that otherwise public void GetDefaultValue<T>() => default(T);
Console.WriteLine(GetDefaultValue<NoDefaultWrapper>()); // ! The above code is blatant, but there are plenty of ways that The only solution that I can see is a CLR change introducing a new generic constraint // New compile error (warning?)
public void GetDefaultValue<T>() => default(T);
// Works
public void GetDefaultValue<T>() where T : default => default(T); Which is a breaking change and therefore should not happen. |
@jnm2 |
They have no good solution for this. Essentially, I think the best we can do is add an analyser which suggests adding an attribute to a generic method which calls default(T), and warn when using a non-defaultable type as a type parameter for such a method. |
Changing default T in a generic setting to mean a nullable T, as suggested in that LDM, works for non nullable reference types as-is, but would require CLR changes for nodefault structs. However, since the Jitter creates a new version of a generic type for every struct, the CLR changes aren't that drastic. However it would essentially restrict this to a CoreCLR only feature. |
Seeing as #146 is championed, closing this issue. |
My proposal at #1981 wasn't very well received, so I've taken on some of the feedback, and come up with a new, simpler, proposal.
As an aside, I would like to assure you that although I call them non-defaultable structs you can still call default(T). What I really mean is non-uninitialisable structs. We'll get to that later.
Ok so onto the proposal.
Currently you can't declare a default constructor or field initialisers with structs.
This leads to a number of issues:
Proposed Solution
The first step is obvious. Allow structs to declare default constructors and field initialisers.
A proposal #99 already exists to do this. However it lead to a lot of controversy because many developers thought default (T) and new T() should be the same.
My solution to this is relatively simple. We should do the same thing as we do for non-nullable reference types. Namely, for non-nullable reference types, the default value is an illegal value, so we force all non-nullable reference types to be initialised.
Similarly we should simply force all instances of a struct with a default constructor declared to be initialised.
If you don't want to initialise such a struct immediately, it must be declared nullable.
Assigning default(T) to such a struct should result in a warning, similar to the decision about non-nullable reference types in https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-08-09.md.
Syntax
I'm unsure about this. We could just say that a struct is non-uninitialisable if it declares a default constructor or a field initialiser.
However that is unobvious to the consumer, and makes it too easy for the library writer to make a breaking change without realising.
An attribute could be used, and a default constructor or a field initialiser can not be written unless the attribute exists. However thats not what attributes are for.
I feel like a new keyword is necessary. Perhaps something like
Then usage would be like this:
The text was updated successfully, but these errors were encountered: