dotnet / csharplang

The official repo for the design of the C# programming language
11.38k stars 1.02k forks source link

Allow struct inheritance #936

Closed MkazemAkhgary closed 6 years ago

MkazemAkhgary commented 6 years ago

Please allow very basic inheritance for structs.

I know why its not allowed. Because structs have fixed size but inherited struct could have different size.

https://stackoverflow.com/q/1222935/4767498

Now my proposal is to allow very basic inheritance (and maybe polymorphism to some degree) for structs. With one simple rule

derived structs are only allowed to declare methods and other functionalities that dont incur change in size of struct. polymorphism should not be allowed for properties in structs, but sealed properties can be allowed in base class.

I really need this, please dont rush on this one and complain about violating rules, please dont kill creativity, but if you have strong reason why this should not be allowed, i appreciate to know.

I need this for performance reason. I have parent class with 4 childs. Class is light (only 8 bytes and a reference) and its immutable. Size of all childs are also equal, childs dont introduce extra size.

Even if this requires clr changes, im eager to propose that.

HaloFour commented 6 years ago

@CyrusNajmabadi

Nod, I don't see a reason why it would have to be limited to single inheritance/containment. That's a translation of the Go example of struct containment which does mention multiple structs but doesn't demonstrate it. The C# version is a bit more verbose as I had to deal with constructors but otherwise it's quite comparable to the Go version.

There is the issue of behavior, though. My understanding is that in Go struct containment is strictly syntax candy and the compiler copies the accessible members of the contained struct into the containing struct. A Ferrari is not actually a Car and you can't assign a variable of one to a variable of another. The use of interfaces is required to support that kind of behavior, which is identical to the situation in C#. But the use of an inheritance-like syntax might imply that Ferrari is a Car, not that it is composed of one.

HaloFour commented 6 years ago

@redknightlois

Because they are entirely different things ...

All the rest are different, so in a sense it is actually inheritance with an explicit discriminant key.

That describes a relationship that is very much not inheritance. It does sound very much like a struct with variant members or a tagged union, though. I'm quite used to dealing with these kinds of data structures in COBOL where they are called REDEFINES.

About why do you need them to be different structs instead of a union type? Because you can abuse the type system and how the JIT behaves to build traits and compile super-efficient code.

Is this demonstrably more performant than using a single structure with overlapping members?

redknightlois commented 6 years ago

@HaloFour Definite yes. I am even giving a talk on DotNext Moscow on November showing specifically on how to abuse JIT behavior to do generic metaprogramming using structs as wrapper structures. :) I am going to do a private test stream to tryout the material and gather feedback next week. I will publish the details on my twitter (check on my profile) if you are interested, let me know.

HaloFour commented 6 years ago

@redknightlois

That seems pretty fragile and kind of scary. There are already several different flavors of the JIT out there with several different operating modes each. Exploiting an undocumented behavior to push performance in such a manner might come back to bite you tomorrow if something changes.

I am interested in the results comparing several different strategies here. I am largely of the opinion that you should write the program logically correct and that the JIT should handle that in the most performant manner. If the JIT is treating this "abuse" (your words) in such a manner that it gives you an appreciable benefit I'd be interested in seeing whether or not the JIT could handle the "right way" in the same manner as well.

redknightlois commented 6 years ago

@HaloFour True, but JIT evolves at its own speed. In the meantime you have 2 options, you either play with rules or you dont get to your performance targets. For those where the former is not an option, you have to invest in the latter. On the other hand, we are abusing pretty documented behavior for this though ;) ... unlikely to change overnight or for the worse, if probably it is going to become even better with time. If I have to guess, I would say that we are going to start seeing more and more of those patterns supported straight at the language level instead.

HaloFour commented 6 years ago

@redknightlois

That it does, but I'd hope that now with the CLR/JIT teams working in the open that you'd invest enough time and energy to demonstrate these behaviors and advocate for better performance. It'd be in your best interest to have the JIT support simpler "hacks" (or none at all) while preserving the benefit in the meantime. I imagine that it would suck a great deal if the next release of the CLR included a tweak that would tank the performance of your product.

redknightlois commented 6 years ago

@HaloFour We are sidetracking from the issue. Last word, that is bound to happen, a fact of life so to speak. Ensuring that there are no regressions and that we can exploit all new behavior of even service releases is part of my day job.

CyrusNajmabadi commented 6 years ago

There is the issue of behavior, though. My understanding is that in Go struct containment is strictly syntax candy and the compiler copies the accessible members of the contained struct into the containing struct. A Ferrari is not actually a Car

That's not exactly how i would describe it. The containment is there. i.e. you can write Ferrari.Car.NumberOfWheels. However, what they've done is make things a little less verbose by "promoting" nested struct members upwards as long as it is legal to do so. For example, if you contain two structs that both have a member named "Foo", then this member will not be promoted and you will have to fully 'dot' into the struct to get to that member.

So it's really just containment, just with a nicer way of dealing with the entire structure when dealing with the instance.