Null-restricted types: Why so complicated?
Quân Anh Mai
anhmdq at gmail.com
Fri Jan 19 17:43:34 UTC 2024
Why not just
Range[] a = new Range[100]; // allocate with null values
System.out.println(a[5]); // NullPointerException
By using Range![] you accept that a zero value is acceptable, similar to
how an int[] works. If you do not want the uninitialized values to be
usable then do not use null-restricted type.
Cheer,
Quan Anh
On Sat, 20 Jan 2024 at 01:34, John Bossons <jbossons at gmail.com> wrote:
> Hi Quan Anh,
>
> We're talking past each other.
>
> ME: But in Java idiom that means that a developer can invoke the public
> implicit constructor, which will cause confusion.
> YOU: An implicit constructor can be invoked like any other constructor,
> and it will return an all-zero instance of the corresponding class.
>
>
> Precisely. Which will often not be valid in the application context. It
> should be possible for a constructor to exclude an all-zero instance, such
> as a zero-length Range or an all-null Name (to use two examples in the
> draft spec) without giving up the ability to specify that it is
> null-restricted. Or for the 'real' constructor to be private, invoked from
> a factory method, which is effectively made useless as a protective feature
> if a public constructor is also provided. (If the implicit constructor
> could be specified as private, that would take care of the problem.
> Extending a marker interface is simpler.)
>
> ME: > My further suggestion is that appending ! to a type should mean
> that the default initialized value of an instance (all fields zero) is
> equivalent to null, so that
> > Range![] a = new Range![100]; // allocated with zero values
> > System.out.println(a[5]); // throws NullPointerException
> (zero fields)
> > This better conforms to current idiom, where the initial initialization
> is with nulls and the println invocation on a null array element or field
> throws a NPE.
> YOU: What is the value of this proposal? If you want the all-zero
> instance to be equivalent to null, just do not have any constructor that
> initializes an instance to that state. The whole point of null-restricted
> fields/variables is to indicate that the field/variable is always valid.
>
>
> The issue here is not what the constructor does -- it hasn't been invoked
> yet for the element on which println is invoked -- but rather that the fact
> that the array element is undefined should be capable of being caught.
>
> On Fri, Jan 19, 2024 at 11:48 AM Quân Anh Mai <anhmdq at gmail.com> wrote:
>
>> I forgot to cc valhalla-dev
>>
>> ---------- Forwarded message ---------
>> From: Quân Anh Mai <anhmdq at gmail.com>
>> Date: Sat, 20 Jan 2024 at 00:33
>> Subject: Re: Null-restricted types: Why so complicated?
>> To: John Bossons <jbossons at gmail.com>
>>
>>
>> Hi,
>>
>> > But in Java idiom that means that a developer can invoke the public
>> implicit constructor, which will cause confusion.
>>
>> An implicit constructor can be invoked like any other constructor, and it
>> will return an all-zero instance of the corresponding class.
>>
>> > My further suggestion is that appending ! to a type should mean that
>> the default initialized value of an instance (all fields zero) is
>> equivalent to null, so that
>> > Range![] a = new Range![100]; // allocated with zero values
>> > System.out.println(a[5]); // throws NullPointerException
>> (zero fields)
>> > This better conforms to current idiom, where the initial initialization
>> is with nulls and the println invocation on a null array element or field
>> throws a NPE.
>>
>> What is the value of this proposal? If you want the all-zero instance to
>> be equivalent to null, just do not have any constructor that initializes an
>> instance to that state. The whole point of null-restricted fields/variables
>> is to indicate that the field/variable is always valid.
>>
>> I think you are having some confusion. Null-restriction is the property
>> of a variable/field, i.e. the property of the holder, not of the class
>> itself. The class having implicit constructors simply means that it allows
>> the existence of null-restricted fields/variables. The class can be used as
>> normal with non-null-restricted types. (e.g Range r = null;)
>>
>> Regards,
>> Quan Anh
>>
>> On Sat, 20 Jan 2024 at 00:09, John Bossons <jbossons at gmail.com> wrote:
>>
>>> Thanks for your comments. I was not sufficiently explicit.
>>>
>>> Let me focus on implicit. I guess my dislike is of introducing a 'fake'
>>> constructor into the definition of a class. I say 'fake' because, as I
>>> understand it, the only purpose of the implicit constructor is to indicate
>>> to the JVM/compiler that a never-null instance can be created. But in Java
>>> idiom that means that a developer can invoke the public implicit
>>> constructor, which will cause confusion.
>>>
>>> Maybe it would be better to require a potentially null-restricted class
>>> to extend a marker interface ('extends NeverNullPossible'? Or maybe,
>>> looking ahead to my next comment, 'extends AllZerosIsNull'?). That would
>>> enable the compiler to catch an invalid use of the ! marker in a
>>> declaration, just as the proposed implicit constructor does, while
>>> conforming better to common Java idiom.
>>>
>>> My further suggestion is that appending ! to a type should mean that the
>>> default initialized value of an instance (all fields zero) is equivalent to
>>> null, so that
>>> Range![] a = new Range![100]; // allocated with zero values
>>> System.out.println(a[5]); // throws NullPointerException
>>> (zero fields)
>>> This better conforms to current idiom, where the initial initialization
>>> is with nulls and the println invocation on a null array element or field
>>> throws a NPE.
>>>
>>> As you say, my suggestion means runtime testing to determine if all
>>> fields are zero, which has a performance cost. This will only occur if the
>>> JVM implements the ! specification, which it presumably will only do if the
>>> object is small. And the cost will be small (I am presuming) relative to
>>> savings from allowing the memory footprint to match that of primitives. Am
>>> I wrong? There is value in conforming to current idiom.
>>>
>>> Turning to the LooselyConsistentValue, I withdraw my comments. I
>>> mistakenly presumed that its use would be required, which is false. It
>>> simply enables a single-threaded (or volatile-protected) application to
>>> allow additional inlining, which is harmless.
>>>
>>> John
>>>
>>> On Thu, Jan 18, 2024 at 4:56 PM - <liangchenblue at gmail.com> wrote:
>>>
>>>> Hi John,
>>>>
>>>> On Thu, Jan 18, 2024 at 2:30 PM John Bossons <jbossons at gmail.com>
>>>> wrote:
>>>>
>>>>> Hi all,
>>>>>
>>>>> Maybe I am missing something, but the proposal seems to be trying to
>>>>> do too much.
>>>>>
>>>>> Specifically: Why not simply provide that appending ! to a type
>>>>> specification for an object (field, array element, or parameter) means that
>>>>> that the object is not only null-restricted but also never zero and
>>>>> necessarily non-atomic unless small?
>>>>>
>>>> First, a reminder that some objects cannot be non-atomic, mostly when
>>>> fields have dependencies/constraints on each other: if you have a range,
>>>> you cannot allow its lower bound to be larger than its upper bound.
>>>> Non-atomic representations cannot avoid this pitfall. Also you seem
>>>> to misunderstand non-atomic: if an object is non-atomic, each of its fields
>>>> can update independently from each other, so a 3-d position can be
>>>> non-atomic, but not so for a range. Non-atomicity is dangerous, and it
>>>> should not be the default. However, if an atomic class is small enough,
>>>> like OptionalInt (as now many architecture has like atomic handling of 16
>>>> bytes etc.) JVM may choose to apply non-atomic optimizations to them for
>>>> better performance without violating their object constraints.
>>>>
>>>>>
>>>>> Why complicate the specification with an implicit constructor that a
>>>>> developer will never explicitly invoke? Why permit a developer to 'opt in'
>>>>> to non-atomic?
>>>>>
>>>> The implicit constructor can always be called; its existence asks
>>>> programmers to affirm that the zero-filled inlined instance is a valid
>>>> instance. And this instance is different from a null, as null is a pointer,
>>>> yet the zero-instance has a different size defined by the class layout in
>>>> the stack/heap.
>>>>
>>>>>
>>>>> Sure, that means trying to read a zero value triggers a NPE. That just
>>>>> means that a type that can legitimately have a zero value cannot be
>>>>> specified as null-restricted, since a zero value (e.g. a {null, null} Name)
>>>>> is the equivalent of a null unrestricted value object. Why go beyond that?
>>>>> If a non-null zero value is possible, the type cannot be null-restricted
>>>>> and so can only be an unrestricted JEP 401 value type. End of story.
>>>>>
>>>> You see the inlined zero instance and the null pointer have different
>>>> sizes, and thus they are not exchangeable. Converting the inlined zero
>>>> instance to null to throw NPE is complex and hurtful to performance as you
>>>> will scan unrelated bits for almost every field access.
>>>>
>>>> And for unrestricted value type, yes, they exist and can possibly be
>>>> inlined as well if the restricted type is small enough (i.e. has space for
>>>> extra bit indicating nullity) But reminder, the nullity bit itself isn't
>>>> even non-atomic with (depends on) the rest of the object! You don't want
>>>> the nullity to indicate null while the rest of the object indicate some
>>>> sort of non-null value, which can happen in a non-atomic context.
>>>>
>>>>>
>>>>> With respect to non-atomic, what is new? Yes, unexpected instances
>>>>> may occur without synchronization if the object is larger than the word
>>>>> size of the implementation. Why do we need to extend a
>>>>> LooselyConsistentValue interface to know/permit that?
>>>>>
>>>> Unexpected instances don't occur without synchronization if you use
>>>> finals, such as in Java's String or immutable List.of(). These APIs may
>>>> capture any "permitted value" from the arrays passed in, but once
>>>> constructed, the captured value remains constant no matter which thread
>>>> observes the String/List object reference. (Technically, JVM implements
>>>> this with a store-store fence between end of field writes in the
>>>> constructor and object reference is shared anywhere, and a load-load fence
>>>> between object reference read and field read) Value classes is about the
>>>> safety of final fields in programming instead of the close encounter of
>>>> third kinds of synchronization, volatiles, and fences.
>>>>
>>>>>
>>>>> Can we not keep this 'simple' (if that word has meaning in this
>>>>> context)? What am I missing?
>>>>>
>>>> I think you are missing a bit about how the layout (inlining is
>>>> represented in memory) and value classes (the thread safety its final
>>>> offers) work, and what "non-atomic" means. Feel free to question more.
>>>>
>>>>>
>>>>> John
>>>>>
>>>>>
>>>>> --
>>>>> Phone: (416) 450-3584 (cell)
>>>>>
>>>>
>>>
>>> --
>>> Phone: (416) 450-3584 (cell)
>>>
>>
>
> --
> Phone: (416) 450-3584 (cell)
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-dev/attachments/20240120/e8220914/attachment.htm>
More information about the valhalla-dev
mailing list