Now, one can argue that this is just smoke and mirrors with type erasure and it is but you can already put a Date into a List<Point> if you're so inclined because the JVM doesn't know the difference, hence type erasure. So this is no different.
I'm no JVM expert but from reading the article it seems like the chosen solution for value classes is to treat them all as a single L-type in the JVM where each primitive type is its own L-type. If I read the correctly, it means that if you have a Point value class then on the JVM level you'll be able to stuff any value class into there if you're so incline, just like with List<Point>.
Obviously we need to be concerned with fuzzing (moreso in C++) but here really we're just trying to have sensible defaults that aren't guaranteed because we can't design the language how we want from the ground up without making a new language.
Oh and there is a prosopal for this [2]. Personally, I prefer the Hack version.
And that's a massive problem that they're planing to solve with specialized generics.
> If I read the correctly, it means that if you have a Point value class then on the JVM level you'll be able to stuff any value class into there if you're so incline, just like with List<Point>.
I'm not sure what you mean. An L-type is an object reference. E.g. Ljava/lang/String, Lcom/org/CustomObject. The issue you're conflating is the erasure of List<T> to List<Object> and it's L-type Ljava/util/List.
> I prefer the Hack version
Hack/HHVM didn't have to worry about backwards compatibility.
---
The blog post does a pretty high level overview of the implementation, but my understanding of it is the following:
1. They are adding a new bytecode class flag for value classes. Bytecode descriptor for value classes are exactly the same (L-type).
2. Primitive wrapper classes will become value classes.
The difference from the CLR is that the JVM implementation is backward compatible with linked legacy bytecode. As they can accept a value class instance into their methods due to the same L-type. It's just additional metadata added to allow the JVM to stack allocate the class.
---
Separately, to handle generics:
1. Parameterized container classes will be flagged with a new bytecode to enable parametric attributes on initialization with the additional data of the parameters in the Constant Pool. Bytecode descriptor for classes are exactly the same with type-erasure, e.g. List<SomeType> erased to Ljava/util/List.
2. Initialization of parameterized classes are done with the additional metadata of the type argument stored in the Constant Pool.
3. The runtime does monomorphization of the parameterized class.
It doesn't seem too different to what the CLR does, i.e. runtime monomorphization. The difference is the JVM implementation is backward compatible with older type-erased code, i.e. the restrictions in Java due to type-erasure are exactly the same as before and the L-types don't change. It's just additional metadata added to allow the JVM to monomorphize the classes for performance.
---
In summary, value objects and specialized generics are backwards compatible with legacy bytecode. The JVM handles the compatibility. Of course, the newer bytecode is not forward compatible with older JVMs.
Regarding the 0 value choice in Go, I don't agree that this is worse than null. It simply applies a design constraint that is not usually very hard to satisfy - that the 0 value of your type must have well defined semantics.
If you allow a nonnull field to hold null at any point then it's trivial to leak that out and have nonnull variables holding null all over the program, which is obviously something to be avoided.
Maybe you can say that if you read a nonnull field when it's null, that throws an exception. That might have its own knock-on effects since nobody expects reading a field from a valid reference to throw an exception, but it might be the best way.
Of course, as I said, this ship has long sailed for general Java classes. It would be far too big of a breaking change to add such a limitation today. However, we know for sure this could have been done in Java: this is exactly how record and now value classes work: any call that could expose un initialized fields is in fact disallowed.
There's a whole bunch of specification language describing how constants aren't actually constant in specific situations.
I don't know Kotlin but I assume it does the same thing: until the non-nullable field gets initialized, it holds null and violates the type system.
Kotlin still has a hole where you can run code in "init" blocks which are executed sequentially on object construction; in one, you can call a function that is defined after an unmodifiable property, and it will see the uninitiailized value.