Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing java interfaces for Rust types #129

Open
nikomatsakis opened this issue Mar 14, 2024 · 2 comments
Open

Implementing java interfaces for Rust types #129

nikomatsakis opened this issue Mar 14, 2024 · 2 comments
Assignees

Comments

@nikomatsakis
Copy link
Member

I observe that many Java APIs take "callbacks" of some kind. Often this has the form

Eg eg = new EgBuilder()
    .onEvent(new EventCallback() {
        int counter = 0;
        void eventArrived(Event e) {
            counter += 1;
            System.out.println("Event " + counter + ": " + e.toString());
        }
    })
    .build();

though of course for simple enough interfaces you can use the .onEvent(e -> ...) lambda syntax instead of an inner object.

I'd like Rust users to be able to call these APIs conveniently. I have a branch that is most of the way there. This is how e.g. I imagine it working. Given this Java code.

package eg;

interface EventCallback {
    void eventArrived(Event e);
}

record Event { ... }

class EgBuilder {
    EgBuilder onEvent(EventCallback ec) { .. }
    Eg build();
}

You could write Rust code like

duchess::package! {
    package eg;
    interface EventCallback {*}
    record Event {*}
    class EgBuilder {*}
}

struct CounterCallback {
    counter: AtomicUsize,
}

// Implement the Java interface for the Rust type
#[duchess::implement_java_interface(eg::EventCallback)]
impl CounterCallback {
    fn on_event(&self, event: &eg::Event) -> duchess::Result<()> {
        let v = self.counter.fetch_add(1, SeqCst);
        let s: String = event.to_string().to_rust().execute()?;
        println!("event {v} is {s}");
        Ok(())
    }
}

// Invoke builder and supply a `CounterCallback` Rust object
fn invoke_builder() {
    let eg = EgBuilder::new()
        .on_event(CounterCallback::default())
        .build()
        .global()
        .execute();
    ...
}

The intent is that

  • The CounterCallback will be "moved" into a Java object -- when that object is collected by the GC, a call will be made to the Rust code to drop CounterCallback
  • when the Java code calls onEvent it will be routed to the Rust code
@nikomatsakis nikomatsakis self-assigned this Mar 14, 2024
@nikomatsakis
Copy link
Member Author

I'm working on this in a branch.

@nikomatsakis
Copy link
Member Author

nikomatsakis commented Mar 14, 2024

Some interesting questions:

  • This has to generate some Java "glue code" class. I am planning to have a mode where, upon initialization, the Rust code "injects" the new Java class into the JVM, simplifying distribution.
  • But another option would be to generate a .class file and extend duchess with some kind of tool to create a JAR.

To interface with the GC, we leverage the JVM's Cleaner class. This approach internally creates a PhantomReference (and a Runnable...) for each Rust object that is embedded in a Java object. This will scale fine for a "reasonable" number of Rust-to-Java objects. It would be a problem if callbacks were used in very large quantities. The examples I'm most interested in tend to be small in number though, more like configuration settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant