Value types, generic specialization, boxing - a quick skim makes it looks like they picked the same choices.
The false dichotomy of
> A struct in C# has identity and mutation, so the semantics of copying on assignment or passing have to be precisely defined, which gives a heavier model for the programmer and less freedom for the runtime.
Doesn't really match with what they're describing. While yes, it will not have identity in a java class ref sense, it of course will still have identity in being a unique structure in memory at a certain address. This is just splitting hairs about Java nomenclature.
No, it will not. The design allows multiple objects to share one structure in memory across multiple records, or not have such a structure at all (see Scalarization in the article).
Again, not trying to turn this into a .NET vs Java thing, I'd have been much happier if they reached some new and interesting conclusions.
Well, it is - because they had to make it with almost perfect backwards compatibility for one of the most popular languages with trillions of lines of code produced over decades.
Sure, adding it to a new language is not hard. Adding it to Java which has primitives, generics and boxing, finding ways that seamlessly cover the differences between objects and primitives, while trying to plan for the future is hard.
As a general note, if you come to the conclusion that one of the best designer teams on Earth "basically copied what .NET did from year 1 is not a good look", then maybe your mental model needs adjusting on how these stuff works? Java has a public mailing list, you can browse through the related discussions. Implementation is the least of these things. But I can assure you they most definitely know what they are doing.
At what cost? A key benefit of value types is improved performance but AFAIK Valhalla doesn't even let you pass them by reference. Efficiently passing them through registers is great but won't help you out with larger value types.
So where in other languages, the struct/class taxonomy is binary, Java allows more granular control, reflection the semantics of the underlying domain. Snd as it turns out, structs have a wide range of footguns, especially in a parallel context.
For example, Imagine you have an api like `void do(List<Foo> foos)`. In the erasure environment of the JVM that looks like `void do(List foos)`. From python it's pretty easy to call with a `foos = [Foo()]`. But not so much if your python implementation needs to figure out how and if it can coarse it's `List` type into a `List<Foo>` type.
Having reified generics in the CLR just lets you store more type information. There isn’t much of a trade off for CLR end-users.
Compare this to the constraints and workarounds that Kotlin and Scala have due to type-erasure on the JVM.
This is fine if you hand-roll all your code yourself, but I often use mapping libraries to lower the code footprint and the problems resulting from schema changes are subtle and fly under the radar. This is different from classes with hard construction guarantees, which Java would offer with their "integrity by default" mantra. Where you can opt out of integrity for performance benefits (which is also part of the design).
And Nullability in C# is an absolute nightmare. The type system has completely different rules for nullable types that generalize over classes and structs and there is no generic such as a "Nullable type".
It's just lots of minor annoyances that don't form a cohesive whole.
Structs are values, classes are entities with encapsulation.
The shape of the state would be structural. Whether or not the data in that shape is valid is behavioral.
Structs are useful when working with spans of memory.
Another example of a good usage of struct is Guid, which is 128 bits of data packed together.
The C# equivalent to Java ‘value class’ would be a class with a struct encapsulated for data. The data is flattened and allocated on the heap like Java. Similarly, escape analysis could stack allocate the class at runtime.
For me, a struct in C/C# can be modified and is passed by copy while a value class can not be modified and is passed by value.
I do not think you can do stack allocation in Java.
C# copies C++ behavior where you can pass a struct by value or reference, and you can mark the parameter as readonly. C# also has in/out parameters. Essentially, you can program in C# exactly like you would in C++.
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
The footgun with C# structs are that you can accidentally box them onto the heap. To avoid that you can define `ref struct`s that cannot be boxed. `ref struct`s follow the C# disposable pattern.
https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
The mutability difference is that part of a struct can be modified in place, which value classes can’t: the value of a complete value-class variable (or array slot) can only be modified (reassigned) as a whole. This is presumably because object references to value-class objects can be created, and those objects should be immutable so their identity doesn’t matter.
The ramifications for backwards compatibility is that the JVM won't have CLR features such as stackalloc (allocation of blocks of memory on stack), ref parameters (pass-by-reference of stack allocated value types), and all the other low-level/high-performance programming features available in the CLR.
Why couldn't it be done? Like sure, it won't happen in every case (e.g. too big value classes), but for a typical local variable of a value type, it's a trivial optimization to make it stack allocated. Even now it happens quite often (requires escape analysis), but the semantics change of value classes allows for it to be done "freely".
Java ‘value class’ only flattens if the total size of the class data fits within an atomic read/write op. You can force it to flatten, but you may have tearing like C# struct.