Forward declaring the identity of an object

David Alayachew davidalayachew at gmail.com
Thu Jul 6 18:27:36 UTC 2023


Hello RedIODev,

Thank you for your response! And thank you for your patience, the holidays
took up a lot of time.

Let me preface this by saying that I won't have many (if any) satisfying
answers for you. A lot of these details seem to go deep into the guts about
how the JVM work, and while I am happy to learn if you can point me to some
resources, the fact is, I just don't have that information in my head at
the moment.

Regardless, I'll attempt each of your bullets.

> I think the biggest problems would be
>
> 1. How do you mark a memory location as allocated (has
>    identity aka memory address) but should still be
>    treated as null which changes later without any
>    notice? It's like a reverse WeakReference

Not sure.

To explain the (very nebulous/shapeless) concept I had in my head, here it
goes.

I believe that the concept of a circular reference is an inherently
identity-based operation. Therefore, we must exclude primitives and value
classes from the equation.

Next, I am of the assumption that there exists a point in time where memory
has been allocated, but the data has not been written to memory yet. My
hope and prayer was that we could allow actions to occur between those
specific 2 actions safely by allowing the compiler to "prove" that the
allocation AND memory write will eventually happen before any dereferencing
occurs.

As a result of that, you have an address that you can pass around to other
variables, but they are not permitted to dereference it until it is
actually initialized with data -- aka, the memory write.

Based on this ethereal concept, I don't know that the data needs to be
marked in any way at all. We were taking 2 actions that were essentially
contiguous, and breaking them up so that subsequent "safe" actions could
occur in between the 2.

Please let me know if I am wildly off of the mark here.

> 2. How would you represent it syntactically it's neither
>    null nor a valid object it's a in between state
>    previously unknown.

Truthfully, I don't really care much about what the syntactic
representation looks like as long as it is immediately clear and obvious.

The semantics, however, are a little different.

I would treat whatever syntactic choice we use as a sort of "hall pass" for
definite assignment. Basically, we have the variable uninitialized as
usual, but then areas where we want to do an illegal forward reference, we
put our "hall pass" in front of the variable to signify that it is "safe to
use uninitialized".

For example.

class Node
{

    final Node next;

    Node(Node next)
    {
        this.next = next; //no dereferencing has occurred for the entirety
of this constructor, and thus, it is "safe".
    }

}

final Node rock, paper, scissors;

rock = new Node(__uninit scissors); //the "hall pass", which triggers the
scissors memory location to be allocated
paper = new Node(rock);
scissors = new Node(paper); //the scissors memory allocation is now being
written to, completing initialization

> 3. There would be many changes in the language necessary
>    to make this feature work reliably and the use beyond
>    niece things like circular references in immutable
>    datastructures are probably low.

Frankly, the primary reason why I feel this feature should exist is for
clarity's sake. We are allowing an object to represent a relationship more
directly. If a references b and b references a, then just bootstrap them
into each other and be done with it! Introducing indirection, mutability,
reflection, or piles of builder code boilerplate when what is needed is a
direct relationship sounds to me like sidestepping a problem by using
Java's other strengths and points of flexibility. And while that strength
and flexibility is both useful and powerful, it adds overhead that makes
problems harder and harder to reason about. I think removing all of that is
making coding simpler, and that is a goal worth pursuing imo.

> 4. This would also bring up many questions in which cases
>    the memory behaves like a object and in which case
>    null. Like synchronization, null checks, instanceof,
>    etc.
>
> It's likely not worth the complexity and the cost.


Ideally, this should not add any new runtime features, but instead, should
be separating pre-existing features into separate actions. My hope is that
this can be an "unsafe" operation that can be proven "safe" by the compiler.

And yes, there is a limit to what the compiler can prove. For example, if
the "constructor" in the example was actually a method on an interface,
well we don't know what the implementing method does. Maybe the
implementing method dereferences the variable, which then begs the question
of what failure should look like.

I am of the mindset that we should treat failure the same way that we do in
the nonatomic concept from Valhalla and mark that as a cost developers can
opt into to get the tradeoffs they want.

As for what should literally happen during failure, well based on what I
described, you can end up with an object containing garbage data. Of
course, the compiler can protect you from a vast majority of these
problems, but you opt into getting garbage data back if you use this
feature incorrectly.

Please let me know your thoughts.

Thank you for your time and questions!
David Alayachew
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230706/b2198e43/attachment.htm>


More information about the amber-dev mailing list