I've been let down by structs in C# repeatedly. First of all, there are no constructor guarantees and you can never fully avoid them representing an illegal state. Which, wouldn't be so bad if there was some kind of post-construction validation, but this also isn't part of the language.
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.
I hate to say it. But thats user error. The struct paradigm is different from classes. Structs are meant to be plain-old data types; simply a typed span of memory.
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.
This is what makes the nuances of Valhalla's Value types so compelling. The idea are granular structs with "Integrity by default", where you can selectively give up constraints on class design to get performance characteristics.
Structs in most languages simply bunch a couple constraints together to get another set of performance benefits, but there's no law stating that they couldn't be singled out. In the design of Valhalla, it states that types can come in 4 buckets:
2: Value Based classes (no mutability, but full integrity and dense memory layout)
3: Implicitly constructed values (forced empty default constructor for swift bulk array initialization)
4: Tearable Values (No cross-field integrity during runtime for parallel access)
And I bet that for a vast majority of developers, #4 will come to a shocking surprise, thinking "values are threat safe" because they are told to use immutables.
This way of splitting up structs is the real interesting part of Valhalla, but this shitty AI-generated article buries everything interesting.
Imo #4 is why it’s not that useful. If the data is larger than an atomic read/write op the data isn’t flattened and it’s a regular object with value equality and immutability.
You have to opt into force flattening, and then it’s the same as a struct, except it’s still heap allocated without escape analysis. You still have to implement synchronization to prevent tearing.
Static code analysis can give you a warning for potential tearing of structs.
DotNext.Threading provides Atomic<T> to enable high-performance atomic operations on structs without heap allocation.
The design of value classes just seems counteractive to its purpose: memory management. If I want to manage contiguous blocks of memory, let me manage contiguous blocks of memory. If I want to allocate something on the stack, let me allocate something on the stack.
The paradigms of struct vs object are too different and they’re trying to combine them into one.
Thanks, I'm always happy to learn more about .net and the CLR.
Regarding #4, is this actually a done deal? I haven't dug into the JVM specifics, but I thought they would avoid allocating objects. And for now they just want to get the model right, while continue to optimize as time moves on. I think that's the right approach.
I actually see this way less critical because if you truly have performance-critical usage of structs, you know what you are doing. And if you know what you are doing, you will know about opting-in.
And for everything else? I think it's nice to have a range of benefits that come from having a value type without handing a gun to a monkey. Because the feature will be misused by people that don't know about tearing, thinking "value" is a free performance upgrade. And I do believe that it is the right mental model to reason about it.
I just don't see the huge issue. If the CLR has a way to provide atomic access to non-tearable structs, surely the JVM can too? We are talking CPU instructions here after all. Or am I missing something?
It is beyond me why I would get downvoted for legitimately pointing out shortcomings. I find it honestly frustrating how some people believe that “their language is best”. Until you point out real existing inconsistencies…
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.