[foreign] RFC simplify callback API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Nov 28 17:48:54 UTC 2018


Hi,
We currently have a Callback<T> type which exposes two functionalities:

* returns the entry point (a Pointer) of the underlying native stub
* allows Java code to view it as a functional interface (of type T)

While this is ok, I've always been a bit puzzled that we needed to 
separate the Callback interface from the functional interface which 
represents it in the Java world. Let's consider a concrete case (from 
StdLIbTest):

void qsort(Pointer<Integer> base, int nitems, int size, 
Callback<QsortComparator> comparator);

@NativeCallback("(u64:i32u64:i32)i32")
interface QsortComparator {
     int compare(Pointer<Integer> u1, Pointer<Integer> u2);
}


The fact that the qsort API has to say Callback<QsortComparator> seems a 
bit cumbersome; of course there's a good reason why it's this way: we 
wanted to force an explicit callback instantiation (via 
Scope::allocateCallback) and move the responsibility of allocating 
callback objects onto clients. So, having a different type in the qsort 
signature force the client to think about the fact that a callback has 
to be allocated here.


Thinking more generally about this problem, and how to improve over the 
status quo, there are two issues here: (i) on the one hand we'd like 
some functional interface to be available somewhere, so that we can 
express the callback code in terms of a lambda expression; (ii) on the 
other hand we want to protect the binder code from running with random 
functional interface implementations. The status quo achieves these 
objectives by moving the functional interface 'away' from the user - 
wrapping it in a Callback<F> type (where F is the functional interface).


Another solution to achieve this would be to have a Callback interface 
defined as follows:

interface Callback<T extends Callback<T>> extends Resource {

     default Pointer<?> ptr() {
         throw new UnsupportedOperationException("Invalid callback!");
     }

     @Override
     default Scope scope() {
         return ptr().scope();
     }
}


That is, a Callback still has a method to obtain a pointer (the entry 
point of the callback) - but this time we just reuse the ptr() method 
declared on the Resource interface. Also note that the ptr() method has 
been defaulted with some throwing code. This achieves two effects:

1) it allows a subclass of Callback to still act as a functional 
interface (only one abstract method)
2) it gives an error whenever the user is attempting to pass an 
unwrapped lambda to some native function


This would allow us to rewrite the above example as follows:

void qsort(Pointer<Integer> base, int nitems, int size, QsortComparator 
comparator);

@NativeCallback("(u64:i32u64:i32)i32")
interface QsortComparator extends Callback<QsortComparator> {
     int compare(Pointer<Integer> u1, Pointer<Integer> u2);
}


Now, I'm well aware that this is not a 100% clean approach; but there 
seem to be some advantages to it that, I think, are worth discussing:

* callbacks now have a common supertypes, as structs do - which means 
the various methods Scope::allocateCallback and LayoutType::ofFunction 
can be expressed with much stronger static guarantees

* clients no longer have to call 'asFunction' on the callback object to 
be able to call the underlying Java method - they can just invoke that 
on the Callback

* APIs feel less cluttered - e.g. the signature of qsort just says 
'QsortComparator' rather than 'Callback<QsortComparator>'

* some of the irregularities surrounding callbacks in LayoutType are 
gone (e.g. we no longer need the ad-hoc getFunctionalIntf() impl method, 
we can just rely on the carrier)

* we no longer need CallbackImpl

* allocating a callback is much more similar to allocate a struct: we 
fetch a pointer and we allocate an instance of the synthetic class 
(generated by the binder for us)


The patch below is an example of how this can be made to work:

http://cr.openjdk.java.net/~mcimadamore/panama/simpler_callback_intf_v2/


Is this a simplification worth exploring? Or is lumping callback and 
functional interfaces a step too far?

Maurizio








More information about the panama-dev mailing list