33
loading...
This website collects cookies to deliver better user experience
Asmble:
Asmble is a compiler that compiles WebAssembly code to JVM bytecode. It also contains an interpreter and utilities for working with WASM code from the command line and from JVM languages.
Asmble is released under the MIT License but is not actively maintained (the last commit is from 2 years ago).
GraalVM:
GraalVM is a high-performance JDK distribution designed to accelerate the execution of applications written in Java and other JVM languages along with support for JavaScript, Ruby, Python, and a number of other popular languages. GraalVM’s polyglot capabilities make it possible to mix multiple programming languages in a single application while eliminating foreign language call costs.
GraalVM allows to run LLVM bitcode. Rust can compile to LLVM. Hence, GraalVM can run your Rust-generated LLVM code along with your Java/Scala/Kotlin/Groovy-generated bytecode.
jni crate:
This crate provides a (mostly) safe way to implement methods in Java using the JNI. Because who wants to actually write Java?
JNI has been the way to integrate C/C++ with Java in the past. While it's not the most glamorous approach, it requires no specific platform and is stable. For this reason, I'll describe it in detail in the next section.
abstract
. Alternatively, they can be native
: a native method delegates its implementation to a library.public native int doubleRust(int input);
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgs>
<arg>-h</arg> <!--1-->
<arg>target/headers</arg> <!--2-->
</compilerArgs>
</configuration>
</plugin>
#include <jni.h>
#ifndef _Included_ch_frankel_blog_rust_Main
#define _Included_ch_frankel_blog_rust_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ch_frankel_blog_rust_Main
* Method: doubleRust
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_ch_frankel_blog_rust_Main_doubleRust
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
cargo new lib-rust
[package]
name = "dummymath"
version = "0.1.0"
authors = ["Nicolas Frankel <[email protected]>"]
edition = "2018"
[dependencies]
jni = "0.19.0" // 1
[lib]
crate_type = ["cdylib"] // 2
jni
cratecdylib
is for dynamic system libraries that you can load from other languages. You can check all other available types in the documentation.#[no_mangle]
pub extern "system" fn Java_ch_frankel_blog_rust_Main_doubleRust(_env: JNIEnv, _obj: JObject, x: jint) -> jint {
x * 2
}
no_mangle
macro tells the compiler to keep the same function signature in the compiled code. It's crucial as the JVM will use this signature.extern
in Rust functions to delegate the implementations to other languages: this is known as FFI. It's the same as we did in Java with native
. However, Rust also uses extern
for the opposite, i.e., to make functions callable from other languages.x
is a jint
, an alias for i32
. For the record, here's how Java primitives map to Rust types:Java | Native | Rust |
---|---|---|
boolean |
jboolean |
u8 |
char |
jchar |
u16 |
byte |
jbyte |
i8 |
short |
jshort |
i16 |
int |
jint |
i32 |
long |
jlong |
i64 |
float |
jfloat |
f32 |
double |
jdouble |
f64 |
jsize |
jint |
cargo build
dylib
extension; on Linux, it will have a so
one, etc.System.load(filename)
and System.loadLibrary(libname)
.load()
requires the absolute path to the library, including its extension, e.g., /path/to/lib.so
. For applications that need to work across systems, that's unpractical. loadLibrary()
allows you to only pass the library's name - without extension. Beware that libraries are loaded in the location indicated by the java.library.path
System property.public class Main {
static {
System.loadLibrary("dummymath");
}
}
lib
prefix is not part of the library's name.public class Main {
private int state;
public Main(int state) {
this.state = state;
}
public static void main(String[] args) {
try {
var arg1 = Integer.parseInt(args[1]);
var arg2 = Integer.parseInt(args[2]);
var result = new Main(arg1).timesRust(arg2); // 1
System.out.println(arg1 + "x" + arg2 + " = " + result);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Arguments must be ints");
}
}
public native int timesRust(int input);
}
arg1 * arg2
native
method looks precisely the same as above, but its name. Hence, the generated C header also looks the same. The magic needs to happen on the Rust side.JNIEnv
and JObject
parameters: JObject
represents the Java object, i.e., Main
and JNIEnv
allows accessing its data (or behavior).#[no_mangle]
pub extern "system" fn Java_ch_frankel_blog_rust_Main_timesRust(env: JNIEnv, obj: JObject, x: jint) -> jint { // 1
let state = env.get_field(obj, "state", "I"); // 2
state.unwrap().i().unwrap() * x // 3
}
"I"
for int
.state
is a Result<JValue>
. We need to unwrap it to a JValue
, and then "cast" it to a Result<jint>
via i()
native
, generating the C header file, and using the jni
crate. We have only scraped the surface with simple examples: yet, we've laid the road to more complex usages.