Revisiting default values
daniel.smith at oracle.com
Tue Jun 29 19:56:13 UTC 2021
> On Jun 29, 2021, at 11:54 AM, Kevin Bourrillion <kevinb at google.com> wrote:
> Sorry for quietness of late.
Glad to have you back!
Unfortunately, there's not much new to report in this area, other than the fact that we are aware that more design and prototyping work is needed.
Here's an open task to prototype an initial javac-only strategy:
> Some new thoughts.
> • Default behaviors of language features should be based first on bug-proof-ness; if a user has to opt into safety that means they were not safe.
> • `null` and nullable types are a very good thing for safety; NPE protects us from more nasty bugs than we can imagine.
> • A world where all user-defined primitive classes must be nullable (following Brian's outline) is completely sane, just not optimized.
These are good principles, and I'm sympathetic to them (with the implication that the right "default default" is null/<invalid>*; using a different default should be opt-in).
(*By <invalid>, I mean the all-zero-bits instance of a no-good-default class, without picking a particular semantics for that value.)
But... these principles potentially conflict with engineering constraints. E.g., I can imagine a world in which a no-good-default primitive class is no better than an identity class in most use cases, and at that point, we're best off simply not supporting the no-good-default feature at all. (With the implication that many of the primitive-candidate classes we are imagining should continue to be identity classes.) I don't like that world, and I don't know how realistic it is, but there's pressure coming from that direction.
To move forward on the "what is the best default default?" question, we're going to need more engineering on no-good-default classes, and get a better sense of their performance characteristics.
> • (We'd like to still be able to fashion a non-nullable type when the class itself allows nullability, but this is a general need we already have for ref types and shouldn't have much bearing here. Still working hard on jspecify.org...)
I think we're pretty locked in to:
- Some primitive class types like Complex must be non-nullable (for compactness)
- We won't (at least for now) support non-nullable types in full generality
Always possible that we'd want to step back and revisit this design, but it's pretty mature.
Speaking of orthogonality, there *is* an open question about how we interpret <invalid>, and this is orthogonal to the question of whether <invalid> should be the "default default". We've talked about:
- It's interchangeable with null
- It's null-like (i.e., detected on member access), but distinct
- It's a separate concept, and it is an error to ever read it from fields/arrays
All still on the table.
(And within each of these, we still need to further explore the implications of JVM vs. language implementation strategies.)
> • It's awkward that `Instant` would have to add a `boolean valid = true` field, but it's not inappropriate. It has the misfortune that it both can't restrict its range of values and has no logical zero/default.
> • A type that does have a restricted range of legal values, but where that range includes the `.default` value, might do some very ugly tricks to avoid adding that boolean field; not sure what to think about this.
How we encode <invalid> is an interesting question that deserves more exploration. There's a potential trade-off here between safety and performance, and like you I'm inclined to prioritize safety. Maybe there are reasonable ways we can get them both...
> • Among all the use cases for primitive classes, the ones where the default value is non-degenerate and expected are the special cases! We use `Complex` as a go-to example, but if most of what we did with complex numbers was divide them by each other then even this would be dubious. We'd be letting an invalid value masquerade as a valid one when we'd rather it just manifest as `null` and be subject to NPEs.
Complex and friends are special cases, but they're also the *most important* cases. I'd really prefer not to have to pick, but if forced to, it may be more important for primitive classes to support optimally the 10% "has a good default" cases (roughly, those that are number-like) than the 90% "no good default" cases (roughly, those that wrap references).
> • If we don't do something like Brian describes here, then I suppose second-best is that we make a lot of these things ref-default (beginning with Instant and not stopping there!) and warn about the dangers of `.val`
I'm not a big fan of this approach. It gives you the illusion of safety (well-written code only sees valid values) but blows up in unpredictable ways when a bug or a hostile actor leaks <invalid> into your program. If we don't offer stronger guarantees, and your code isn't willing to check for <invalid>, you really shouldn't be programming with a primitive class.
More information about the valhalla-spec-observers