Null-restricted types: Why so complicated?
Brian Goetz
brian.goetz at oracle.com
Fri Jan 19 21:34:50 UTC 2024
> Thanks for your comment and the reference to Brian's 2021 document.
> You're right about the series of steps, I get that. My comments on
> undefined array elements really come down to saying that it's bad
> practice to specify arrays of null-restricted elements except where
> you're sure undefined elements are never read before being defined.
I think part of why you are confused here is that you are looking at the
features individually, rather than the big picture.
Identity restriction is both semantically useful, and potentially admits
certain optimizations.
Implicit construction is semantically useful (it says "you can use me
without initialization", just as types like `int` do), and potentially
admits certain optimizations.
Non-nullity is semantically useful, and potentially admits certain
optimizations.
We do not want to define "performance features" here; we want to define
semantically useful features of classes, for which we can reliably
perform desired optimizations when the right semantic constraints are in
place. The hard part was identifying which semantic features those were.
Fields of type `String!` (and arrays whose elements are `String!`) are
semantically useful (lots of people would love to have them even if they
never get value types), even though we can't flatten them at all. But
to ensure their usefulness, we need to ensure they are initialized
before use (or, tolerate NPEs in badly written programs, such as when
`this` escapes construction.)
On the other hand, fields of type `Integer!` (or arrays whose elements
are `Integer!`) can be flattened safely regardless of races,
this-escapes, etc -- because `Integer` is implicitly constructible.
(And, this also allows us to guarantee that it will *never* be null, as
opposed to the `String!` case, where such a guarantee always comes with
caveats.) But the cost of this is that we have to know which classes
are like String and which are like Integer, and the implicit constructor
is what signals this.
On the other other hand, fields/arrays of `LocalDate!` _could_ be
flattened, but only if we are absolutely 100% sure (no caveats) that
they are guaranteed to be initialized before writing. (Failure to get
this right is much worse than an errant NPE; we've violated the
integrity of the object model.)
We want the language and runtime to have access to all the information
it needs to provide good performance and safety in all of these cases.
You might dislike the syntax choice behind implicit constructors (maybe
you prefer a class modifier like `implicitly-constructible`), but this
isn't the place for syntax debates. The important thing here is that
whether a class can be implicitly constructed or not appears, after many
many hours of analysis, to be an imcompressible element of the design,
unless we are willing to compromise on either safety or flattening. It
is natural and healthy to wonder "can this be simplified further" ...
but best to do so with awareness that we may be staring at Chesterton's
Fence (https://fs.blog/chestertons-fence/).
More information about the valhalla-dev
mailing list