Safe Rust <---> GraalVM Polyglot bindings using procedural macros

Overview

graal-bindgen

graal-bindgen generates safe bindings between Rust and Graal Polyglot so that you can use Java types and methods as if they were native to Rust.

Overview

The class macro is the primary way to generate bindings to Java types; it will generate a struct (with generics if specified) that implements Pass and Receive and has all the methods you give stubs for. The methods generated can be used like normal rust methods, however mutability is not enforced. The fully-qualified type name should precede a block containing method and constructor stubs. Java primitives like char, int, and byte are aliased to corresponding Rust types.

Building

First, make sure you have cargo-make installed, the GRAAL_HOME environment variable points to the root directory of your GraalVM installation, and the GraalVM LLVM toolchain is installed:

export GRAAL_HOME=[PATH_TO_GRAAL]
cargo install cargo-make
${GRAAL_HOME}/bin/gu install llvm-toolchain

You can then run

cargo make run

to run main.rs, or

cargo make build

to just compile it. Currently, graal-bindgen isn't published on crates.io since its build process is reliant on GraalVM being installed.

TODO

  • Automated generation from Javadoc
  • Generics in generics
  • Generic bounds (CitrusJuice
  • Static field access
  • Function and type declaration from Rust
  • Tests

ArrayList example

The following example uses nested Java java.util.ArrayLists, and then uses the ArrayList#toArray method to convert it to a Polyglot Array.

use crate::polyglot::*;
use std::marker::PhantomData;
use crate::types::jtypes::*;

graal_bindgen_macros::class! [java.util.ArrayList<E> {
    new();
    E get(int index);
    boolean add(E e);
    E[] toArray();
}];

let list = ArrayList::new();
let list_in_list = ArrayList::new();
for i in 0..100 {
    list_in_list.add(i);
}
list.add(list_in_list);
let array_from_list = list.get(0).toArray();
for i in 0..100 {
    println!("{}", array_from_list.get(i).unwrap());
}

Here's what the preceding code would look like in normal Rust using Vec and slices:

let mut vec = Vec::new();
let mut vec_in_vec = Vec::new();
for i in 0..100 {
    vec_in_vec.push(i);
}
vec.push(vec_in_vec);
let slice_from_vec = vec.get(0).unwrap().as_slice();
for i in 0..100 {
    println!("{}", slice_from_vec.get(i).unwrap());
}

A full implementation of java.util.ArrayList can be seen in src/builtins/mod.rs.

Constructor stubs

A stub is inferred to be a constructor if it doesn't have a return type. The Rust name of the constructor must be explicitly declared, and doesn't have to be the name of the type.

class! [java.lang.String {
    new();
    new_from(String original);
}];

will expand to a struct String with the methods String::new and String::new_from:

pub fn new() -> String {
    let polyglot_type = crate::java_type("java.lang.String");
    String::from_polyglot_value({
        unsafe { crate::polyglot::internal::polyglot_new_instance(polyglot_type) }
    })
}
pub fn new_from(original: String) -> String {
    let polyglot_type = crate::java_type("java.lang.String");
    String::from_polyglot_value({
        unsafe {
            crate::polyglot::internal::polyglot_new_instance(
                polyglot_type,
                crate::polyglot::internal::expect_variadic(original),
            )
        }
    })
}

String::new() will be equivalent to calling new String() in Java, and String::new_from(...) will be equivalent to new String(...).

Function stubs

Function stubs are composed of a return value, an optional alias, a name, and arguments.
[alias] ( [ ]* ); Aliases make the Rust function name different to the function name in Java; this is useful when you have an overloaded method since Rust does not support overloading and will not compile if two functions have the same name. If left empty, the Rust binding name is presumed to be the same as the Java function name.

class! [java.util.ArrayList<E> {
    int add_at add(int index, E element);
    int add(E element);
}];

will expand to a struct ArrayList with the methods ArrayList::add_at and ArrayList::add:

pub fn add_at(&self, index: int, element: E) -> int {
    return int::from_polyglot_value({
        unsafe {
            crate::polygot::internal::polyglot_invoke(
                self.ptr,
                crate::polygot::internal::make_cstring("add").as_ptr(),
                crate::polygot::internal::expect_variadic(index),
                crate::polygot::internal::expect_variadic(element),
            )
        }
    });
}
pub fn add(&self, element: E) -> int {
    return int::from_polyglot_value({
        unsafe {
            crate::polygot::internal::polyglot_invoke(
                self.ptr,
                crate::polygot::internal::make_cstring("add").as_ptr(),
                crate::polygot::internal::expect_variadic(element),
            )
        }
    });
}

Pass and Receive

The Pass and Receive traits indicate that describe how a type can safely be passed to and received from Graal Polyglot.

Pass and Passable

The Pass trait requires fn pass(&self) -> T to be implemented. Passable is a marker trait for whether a type is safe to directly pass to polyglot, and because unboxed primitives are passed directly but all other types are passed using a pointer. For all objects, pass should be implemented so that it returns its inner pointer (a *Value) like this:

fn pass(&self) -> *mut Value {
    self.ptr
}

Receive

The Receive trait defines how a Polyglot value can be used to construct a type. Usually, it should just instantiate a struct Self with its backing pointer set to the given *mut Value like this:

fn from_polyglot_value(value: *mut Value) -> Self {
    Self { ptr: value }
}

Generics

class! supports generics; the generic type must be Pass + Receive. Due to the poor design decision of treating *mut Values and primitives differently (even though they can be passed to polyglot directly), Pass makes it so that there needs to be an extra parameter for each desired generic. The first generic parameters are the ones you specify, followed by a Passable bound for each one you specified after. Type inference should sort this out, but if you need to specify explicitly, you can tell Rust to still infer the Passable bounds like this:

let my_arraylist: ArrayList<i32, _> = ArrayList::new();

If something goes really wrong, you can explicitly specify the Passable. For primitives, this will be the same as the main type. For other Objects, this will be *mut Value.

Arrays

Arrays are represented by JavaArray. Currently, creating and updating elements in them has not been implemented and Index cannot be implemented, since the trait requires a reference to be returned. The return value of .get() is an Option; if the index is out of bounds, it will be None, otherwise it will be Some(value_at_index).

You might also like...
A safe Rust FFI binding for the NVIDIA® Tools Extension SDK (NVTX).
A safe Rust FFI binding for the NVIDIA® Tools Extension SDK (NVTX).

NVIDIA® Tools Extension SDK (NVTX) is a C-based Application Programming Interface (API) for annotating events, code ranges, and resources in your applications. Official documentation for NVIDIA®'s NVTX can be found here.

High-level memory-safe binding generator for Flutter/Dart <-> Rust
High-level memory-safe binding generator for Flutter/Dart - Rust

flutter_rust_bridge: High-level memory-safe binding generator for Flutter/Dart - Rust Want to combine the best between Flutter, a cross-platform hot

A memory safe Lua interpreter

Hematita Da Lua Hematita Da Lua is an interpreter for the scripting language Lua, written entirely in 100% safe Rust. Hematita is the portugese word f

Rust based WASM/JS bindings for ur-rust

ur-wasm-js WASM/JS bindings for the ur-rust rust library Getting started Installation Either build the library yourself with wasm-pack or install for

A project for generating C bindings from Rust code

cbindgen   Read the full user docs here! cbindgen creates C/C++11 headers for Rust libraries which expose a public C API. While you could do this by h

Automatically generates Rust FFI bindings to C (and some C++) libraries.

bindgen bindgen automatically generates Rust FFI bindings to C (and some C++) libraries. For example, given the C header doggo.h: typedef struct Doggo

Rust-JDBC bindings

jdbc A Rust library that allows you to use JDBC and JDBC drivers. Usage First, add the following to your Cargo.toml: [dependencies] jdbc = "0.1" Next,

Lua 5.3 bindings for Rust

rust-lua53 Aims to be complete Rust bindings for Lua 5.3 and beyond. Currently, master is tracking Lua 5.3.3. Requires a Unix-like environment. On Win

Objective-C Runtime bindings and wrapper for Rust.

Objective-C Runtime bindings and wrapper for Rust. Documentation: http://ssheldon.github.io/rust-objc/objc/ Crate: https://crates.io/crates/objc Messa

Owner
Alec Petridis
sophomore at gunn high school
Alec Petridis
Distribute a wasm SPA as HTML by wrapping it as a polyglot "html+wasm+zip"

A packer that adds a webpage to WASM module, making it self-hosted! Motivation At the moment, Browsers can not execute WebAssembly as a native single

Andreas Molzer 3 Jan 2, 2023
Safe Rust bindings to Lua 5.1

rust-lua Copyright 2014 Lily Ballard Description This is a set of Rust bindings to Lua 5.1. The goal is to provide a (relatively) safe interface to Lu

Lily Ballard 124 Jan 5, 2023
mruby safe bindings for Rust

mrusty. mruby safe bindings for Rust mrusty lets you: run Ruby 1.9 files with a very restricted API (without having to install Ruby) reflect Rust stru

Anima 200 Oct 12, 2022
Rust bindings for writing safe and fast native Node.js modules.

Rust bindings for writing safe and fast native Node.js modules. Getting started Once you have the platform dependencies installed, getting started is

The Neon Project 7k Jan 4, 2023
A collection of unsound rust functions using entirly *safe* code

A collection of unsound rust functions using entirly *safe* code

null 2 Sep 6, 2022
Python bindings for akinator-rs using pyo3

Akinator-py python bindings for akinator-rs using pyo3 Installation Prebuilt wheels are uploaded onto pypi, if you platform is supported, you can inst

Tom-the-Bomb 4 Nov 17, 2022
Safe interop between Rust and C++

CXX — safe FFI between Rust and C++ This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the m

David Tolnay 4.4k Jan 7, 2023
Safe Rust bridge for creating Erlang NIF functions

Rustler Documentation | Getting Started | Example Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways t

Rusterlium 3.5k Jan 7, 2023
WebAssembly implementation from scratch in Safe Rust with zero dependencies

wain wain is a WebAssembly INterpreter written in Rust from scratch with zero dependencies. An implementation of WebAssembly. Features: No unsafe code

Linda_pp 328 Jan 2, 2023
A minimalist and safe ECS library for rust!

The full ECS (Entity-Component-System) library. Support an Open Source Developer! ♥️ Composed of two smaller libraries: world_dispatcher: the System p

Joël Lupien 124 Dec 19, 2022