Java for Rust
j4rs stands for 'Java for Rust' and allows effortless calls to Java code from Rust and vice-versa.
With the hope that you find this project useful, please consider donating to charity.
…Instancess of Java Lists and Maps can be created with the java_list and java_map functions:
let rust_vec = vec!["arg1", "arg2", "arg3", "arg33"];
// Generate a Java List. The Java List implementation is the one that is returned by java.util.Arrays#asList
let java_list_instance = jvm.java_list(
JavaClass::String,
rust_vec)?;
let rust_map = HashMap::from([
("Potatoes", 3),
("Tomatoes", 33),
("Carrotoes", 333),
]);
// Generate a java.util.HashMap.
let java_map_instance = jvm.java_map(
JavaClass::String,
JavaClass::Integer,
rust_map)?;j4rs uses the InvocationArg enum to pass arguments to the Java world.
Users can benefit of the existing TryFrom implementations for several basic types:
…And for Vecs:
let my_vec: Vec = vec![
"abc".to_owned(),
"def".to_owned(),
"ghi".to_owned()];
// Creates List
let i10 = InvocationArg::try_from(my_vec.as_slice())?;
let another_vec: Vec = vec![0.0, 1.0, 3.6];
// Creates List
let i11 = InvocationArg::try_from(another_vec.as_slice())?;The j4rs apis accept InvocationArgs either as references, or values:
let inv_args = InvocationArg::try_from("arg from Rust")?;
let _ = jvm.create_instance("java.lang.String", &[&inv_args])?; // Pass a reference
let _ = jvm.create_instance("java.lang.String", &[inv_args])?; // MoveThe Instances returned by j4rs can be transformed to InvocationArgs and be further used for invoking methods as well:
let one_more_string_instance = jvm.create_instance(
"java.lang.String", // The Java class to create an instance for
InvocationArg::empty(), // The `InvocationArg`s to use for the constructor call - empty for this example
)?;
let i11 = InvocationArg::try_from(one_more_string_instance)?;To create an InvocationArg that represents a null Java value, use the From implementation with the Null struct:
let null_string = InvocationArg::from(Null::String); // A null String
let null_integer = InvocationArg::from(Null::Integer); // A null Integer
let null_obj = InvocationArg::from(Null::Of("java.util.List")); // A null object of any other class. E.g. ListCustom types, for which there is no TryFrom implementation, are also supported via serialization.
To use a custom struct MyBean as an InvocationArg it needs to be serializable:
#[derive(Serialize, Deserialize, Debug)]
#[allow(non_snake_case)]
struct MyBean {
someString: String,
someInteger: isize,
}Then, an InvocationArg can be created like:
let my_bean = MyBean {
someString: "My String In A Bean".to_string(),
someInteger: 33,
};
let ia = InvocationArg::new(&my_bean, "org.astonbitecode.j4rs.tests.MyBean");And it can be used as an argument to a Java method that accepts org.astonbitecode.j4rs.tests.MyBean instances.
Of course, there should exist a respective Java class in the classpath for the deserialization to work and the custom Java Object to be created:
package org.astonbitecode.j4rs.tests;
public class MyBean {
private String someString;
private Integer someInteger;
public MyBean() {
}
public String getSomeString() {
return someString;
}
public void setSomeString(String someString) {
this.someString = someString;
}
public Integer getSomeInteger() {
return someInteger;
}
public void setSomeInteger(Integer someInteger) {
this.someInteger = someInteger;
}
}(v0.16.0 onwards)
j4rs supports .async/.await viaJvm::invoke_async function.
The function returns a Future, which is completed via the Receiver of a oneshot channel.
In Java side, the methods that can be invoked by invoke_async, must return a Java Future.
When the Java Future completes, the Java side of j4rs invokes native Rust code that completes the pending Rust Future
with either success or failure, using the Sender of the oneshot channel that was created when the invoke_async was called.
For example, assuming we have a Java method that returns a Future:
package org.astonbitecode.j4rs.tests;
public class MyTest {
private static ExecutorService executor = Executors.newSingleThreadExecutor();
// Just return the passed String in a Future
public Future getStringWithFuture(String string) {
CompletableFuture completableFuture = new CompletableFuture<>();
executor.submit(() -> {
completableFuture.complete(string);
return null;
});
return completableFuture;
}
}We can invoke it like following:
let s_test = "j4rs_rust";
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance = jvm.invoke_async(&my_test, "getStringWithFuture", &[InvocationArg::try_from(s_test)?]).await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);Please note that it is better for the Java methods that are invoked by the invoke_async function
to return a CompletableFuture,
as this improves performance.
j4rs handles simple Java Futures that are not CompletableFutures with polling,
using an internal one-threaded ScheduledExecutorService.
This has apparent performance issues.
invoke_async and SendInstances are Send and can be safely sent to other threads. However, because of Send Approximation, the Future returned by invoke_async is not Send, even if it just contains an Instance. This is because the Jvm is being captured by the async call as well and the Jvm is not Send.
In order to have a Future that is Send, the Jvm::invoke_into_sendable_async can be used. This function does not get a Jvm as argument; it creates one internally when needed and applies some scoping workarounds in order to achieve returning a Future which is also Send.
Discussion here.
An Instance may be casted to some other Class:
let instantiation_args = vec![InvocationArg::try_from("Hi")?];
let instance = jvm.create_instance("java.lang.String", instantiation_args.as_ref())?;
jvm.cast(&instance, "java.lang.Object")?;// Create a Java array of Strings `String []`
let s1 = InvocationArg::try_from("string1")?;
let s2 = InvocationArg::try_from("string2")?;
let s3 = InvocationArg::try_from("string3")?;
let arr_instance = jvm.create_java_array("java.lang.String", &[s1, s2, s3])?;
// Invoke the Arrays.asList(...) and retrieve a java.util.List
let list_instance = jvm.invoke_static("java.util.Arrays", "asList", &[InvocationArg::from(arr_instance)])?;When creating an array of primitives, each InvocationArg item needs to be converted to one containing primitive value.
let doubles = vec![0.1, 466.5, 21.37];
// Creates Java `double` primitives within a Rust `Vec`
let double_args: Vec = doubles.iter()
.map(|value| Ok::(
InvocationArg::try_from(value)?.into_primitive()?
))
.collect::>()?
// Creates an instance of `double []`
let doubles_array = InvocationArg::try_from(
jvm.create_java_array("double", &double_args)?
)?;// Assuming the following map_instance is a Map
// we may invoke its put method
jvm.invoke(&map_instance, "put", &[InvocationArg::try_from("one")?, InvocationArg::try_from(1)?])?;Even if auto boxing and unboxing is in place, j4rs cannot invoke methods with primitive int arguments using Integer instances.
For example, the following code does not work:
let ia = InvocationArg::try_from(1_i32)?;
jvm.create_instance("java.lang.Integer", &[ia])?;It throws an InstantiationException because the constructor of Integer takes a primitive int as an argument:
Exception in thread "main" org.astonbitecode.j4rs.errors.InstantiationException: Cannot create instance of java.lang.Integer at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:37) Caused by: java.lang.NoSuchMethodException: java.lang.Integer.(java.lang.Integer) at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getConstructor(Class.java:2151) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.createInstance(NativeInstantiationImpl.java:69) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:34)
In situations like this, the java.lang.Integer instance should be transformed to a primitive int first:
let ia = InvocationArg::try_from(1_i32)?.into_primitive()?;
jvm.create_instance("java.lang.Integer", &[ia]);use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
// Create a JVM
let jvm = JvmBuilder::new().build()?;
// Create an instance
let string_instance = jvm.create_instance(
"java.lang.String",
&[InvocationArg::try_from(" a string ")?],
)?;
// Perform chained operations on the instance
let string_size: isize = jvm.chain(string_instance)
.invoke("trim", InvocationArg::empty())?
.invoke("length", InvocationArg::empty())?
.to_rust()?;
// Assert that the string was trimmed
assert!(string_size == 8);j4rs provides support for Java to Rust callbacks.
These callbacks come to the Rust world via Rust Channels.
In order to initialize a channel that will provide Java callback values, the Jvm::invoke_to_channel should be called. It returns a result of InstanceReceiver struct, which contains a Channel Receiver:
…In the Java world, a Class that can do Native Callbacks must extend the
org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport
For example, consider the following Java class.
No open issues yet, or sync has not completed.