Experiments with Java-Rust interop

Related tags

Command-line duchess
Overview

duchess

Experiments with Java-Rust interop

Instructions

You need the javap tool on your path.

On Ubuntu, I installed java-20-amazon-corretto-jdk/stable, but openjdk-17-jre/stable-security might also work. --nikomatsakis

How to use

This is a README from the future, in that it describes the intended plan for the crate.

What it does

Duchess makes it easy to call Java APIs from your Rust code. It may eventually help with bidirectional support, but right now that is not supported.

How duchess works

Let's suppose that you have a java class me.ferris.Logger:

class Logger {
    public static Logger globalLogger();

    public Logger();

    // Simple, convenient log method
    public void log(String data);

    public void logFull(LogMessage message);
}

class LogMessage {
    public LogMessage(String message);

    LogMessage level(int level);
}

which you can use in your java code to issue simple logs

Logger.globalLogger().log("Hello, world");

or to issue more complex ones

LogMessage m = new LogMessage("Hello, world").level(22);
Logger.globalLogger().log(m);

But now you would like to write some Rust code that invokes this same logging service. What do you do?

TL;DR

For the impatient among you, here is the kind of code you're going to be able to write when we're done. First you declare the java classes you want to work with:

duchess::duchess! {
    me.ferris.Logger,
    me.ferris.LogMessage,
}

and then instead of this java code

Logger.globalLogger().log("Hello, world");

you can write Rust like this

duchess::with_jvm(|jni| {
    use me::ferris::Logger;
    Logger::globalLogger().log("Hello, world").execute(jni);
});

and instead of this Java code

LogMessage m = new LogMessage("Hello, world").level(22);
Logger.globalLogger().log(m);

you can write Rust like this

duchess::with_jvm(|jni| {
    use me::ferris::{Logger, LogMessage};
    LogMessage::new("Hello, world").level(22).execute(jni);
    Logger::globalLogger().log(&m).execute(jni);
});

Huzzah!

What code does the macro generate?

Let's walk through this in more detail. To start, use the duchess! macro to create a Rust view onto the java code. The duchess! macro supports various bells and whistles, but in its most simple form, you just declare a module and list some java classes inside of it.

duchess::duchess! {
    me.ferris.Logger // Always list the java classes by their full dotted name!
}

The procedural macro will create a module named jlog and, for each class that you name, a struct and an impl containing all of its methods, but mirrored into Rust. The structs are named after the full Java name (including the package), but there are type aliases for more convenient access:

mod me {
    pub mod ferris {
        pub struct Logger<'jvm> { ... }    
    
        impl<'jvm> Logger<'jvm> {
            pub fn globalLogger(jvm: Jvm<'jvm>) -> Logger<'jvm> {
                ...
            }

            pub fn log(&self, jvm: Jvm<'jvm>, s: impl AsJavaString) {
                ...
            }

            pub fn log_full(&self, jvm: Jvm<'jvm>, s: &impl AsRef<LogMessage<'jvm>>) {
                ...
            }
        }
    }

    ... // more to come
}

Where possible, we translate the Java argument types into Rust-like forms. References to Java strings, for example, compile to impl AsJavaString, a trait which is implemented for &str and String (but also for a reflected Java string).

pub fn log(&self, jvm: Jvm<'jvm>, s: impl AsJavaString) {
    ...
}

In some cases, methods will reference Java classes besides the one that appeared in the proc macro, like me.ferris.LogMessage:

pub fn log_full(&self, jvm: Jvm<'jvm>, s: &impl AsRef<LogMessage<'jvm>>)

These extra types get translated to structs as well. But these structs don't have impl blocks or methods. They're just opaque values you can pass around:

mod me {
    pub mod ferris {
        // From before:
        pub struct Logger<'jvm> { ... }
        impl<'jvm> Logger<'jvm> { ... }

        // Also generated:
        pub struct LogMessage<'jvm> { ... }
    }

    ...
}

In fact, we'll also generate entries for other Java classes, like

mod me {...}
mod java {
    pub mod lang {
        pub struct Object<'jvm> { ... }
        pub struct String<'jvm> { ... }
    }
}

Finally, we generate various AsRef (and From) impls that allow for upcasting between Java types:

mod me { /* as before */  }
mod java { /* as before */ }

impl<'jvm> AsRef<java::lang::Object<'jvm>> for me::ferris::Logger<'jvm> { ... }
impl<'jvm> AsRef<java::lang::Object<'jvm>> for me::ferris::LogMessage<'jvm> { ... }

impl<'jvm> From<me::ferris::Logger<'jvm>> for java::lang::Object<'jvm>> { ... }
impl<'jvm> From<me::ferris::LogMessage<'jvm>> for java::lang::Object<'jvm>> { ... }

Implementation details

Our structs are actually thin wrappers around jni::JObject:

#[repr(transparent)]
pub struct Logger<'jvm> {
    object: jni::JObject<'jvm>
}

In some cases, the JNI crate only supplies &JObject values. These can be safely transmuted into (e.g.) &Logger values because of the repr(transprent) layout (c.f. rust reference, though I'd like to check on the details here --nikomatsakis). We provide a helper function cast for this purpose.

Creating a jni

The duchess::with_jvm code starts a JVM and invokes your closure. Clearly this needs to be cached, and we have to think about attaching threads and the like.

Deleting locals

It'd be nice to delete locals automatically. I wonder if we can do this via a Drop impl. It'll require a thread-local to get access to the JVM. Seems ok.

You might also like...
Rust API Server: A versatile template for building RESTful interfaces, designed for simplicity in setup and configuration using the Rust programming language.
Rust API Server: A versatile template for building RESTful interfaces, designed for simplicity in setup and configuration using the Rust programming language.

RUST API SERVER Introduction Welcome to the Rust API Server! This server provides a simple REST interface for your applications. This README will guid

A full featured, fast Command Line Argument Parser for Rust

clap Command Line Argument Parser for Rust It is a simple-to-use, efficient, and full-featured library for parsing command line arguments and subcomma

Docopt for Rust (command line argument parser).

THIS CRATE IS UNMAINTAINED This crate is unlikely to see significant future evolution. The primary reason to choose this crate for a new project is if

Quickly build cool CLI apps in Rust.

QuiCLI Quickly build cool CLI apps in Rust. Getting started Read the Getting Started guide! Thanks This is only possible because of all the awesome li

A minimal CLI framework written in Rust
A minimal CLI framework written in Rust

seahorse A minimal CLI framework written in Rust Features Easy to use No dependencies Typed flags(Bool, String, Int, Float) Documentation Here Usage T

▁▂▆▇▁▄█▁ Sparklines for Rust apps

rspark ▁▂▆▇▁▄█▁ Sparklines for Rust apps. Rust port of https://github.com/holman/spark Usage Add this to your Cargo.toml: [dependencies] rspark = "0.2

A readline-like library in Rust.

liner A Rust library offering readline-like functionality. CONTRIBUTING.md Featues Autosuggestions Emacs and Vi keybindings Multi-line editing History

Readline Implementation in Rust

RustyLine Readline implementation in Rust that is based on Antirez' Linenoise Supported Platforms Unix (tested on FreeBSD, Linux and macOS) Windows cm

a Rust library for running child processes

duct.rs Duct is a library for running child processes. Duct makes it easy to build pipelines and redirect IO like a shell. At the same time, Duct help

Comments
  • Stability plans?

    Stability plans?

    Hi :wave: I'm keeping an eye on this project because it might become a modern replacement for our jni-gen, which we wrote before procedural macros existed and is still based on jni v0.20.

    Is duchess intended to (eventially) be more than just an experiment, or is it just too early to say? In the first case we'd be happy to contribute with what's missing for our use cases (e.g. access to Java fields, fast *_unchecked Java method calls, caching of method ids, debug assertions with runtime type checks...); in the second case we'll wait a bit more.

    opened by fpoli 0
  • overloaded methods

    overloaded methods

    We need to figure out how to handle type overloaded methods.

    One option would be to create variant names (e.g., foo1 vs foo2), but that's kind of uncool.

    There are various ways to do type overloading in Rust, even overloading on the number of arguments, but it's a bit complex and I'm not sure how it would work at the moment.

    opened by nikomatsakis 1
Rust-clippy - A bunch of lints to catch common mistakes and improve your Rust code

Clippy A collection of lints to catch common mistakes and improve your Rust code. There are over 450 lints included in this crate! Lints are divided i

The Rust Programming Language 8.7k Dec 31, 2022
Rust-battery - Rust crate providing cross-platform information about the notebook batteries.

battery Rust crate providing cross-platform information about the notebook batteries. Table of contents Overview Supported platforms Install Examples

svartalf 326 Dec 21, 2022
A Rust-based shell script to create a folder structure to use for a single class every semester. Mostly an excuse to use Rust.

A Rust Course Folder Shell Script PROJECT IN PROGRESS (Spring 2022) When completed, script will create a folder structure of the following schema: [ro

Sebastián Romero Cruz 1 Apr 10, 2022
Rust Imaging Library's Python binding: A performant and high-level image processing library for Python written in Rust

ril-py Rust Imaging Library for Python: Python bindings for ril, a performant and high-level image processing library written in Rust. What's this? Th

Cryptex 13 Dec 6, 2022
FTL Rust Demangler is a command-line tool for demangling symbol names that are mangled with the Rust convention

FTL Rust Demangler is a command-line tool for demangling symbol names that are mangled with the Rust convention. It takes a mangled symbol name as input and returns the demangled name

timetravel3 7 Mar 30, 2023
rpm (Rust project manager) is a tool that helps you to manage your rust projects

rpm rpm (Rust project manager) is a open source tool for managing your rust project in an organized way Installation # make sure you have rust install

Dilshad 4 May 4, 2023
auto-rust is an experimental project that aims to automatically generate Rust code with LLM (Large Language Models) during compilation, utilizing procedural macros.

Auto Rust auto-rust is an experimental project that aims to automatically generate Rust code with LLM (Large Language Models) during compilation, util

Minsky 6 May 14, 2023
Rusty Shellcode Reflective DLL Injection (sRDI) - A small reflective loader in Rust 4KB in size for generating position-independent code (PIC) in Rust.

Shellcode Reflective DLL Injection (sRDI) Shellcode reflective DLL injection (sRDI) is a process injection technique that allows us to convert a given

null 242 Jul 5, 2023
This rust compiler backend emmits valid CLR IR, enambling you to use Rust in .NET projects

What is rustc_codegen_clr? NOTE: this project is a very early proof-of-concept This is a compiler backend for rustc which targets the .NET platform an

null 252 Sep 7, 2023
RustGPT is a ChatGPT UI built with Rust + HTMX: the power of Rust coupled with the simplicity of HTMX 💚

RustGPT ??✨ RustGPT.Blog.Post.mp4 Welcome to the RustGPT repository! Here, you'll find a web ChatGPT clone entirely crafted using Rust and HTMX, where

Bitswired 529 Dec 4, 2023