[HN Gopher] Lessons from Mixing Rust and Java: Fast, Safe, and P...
___________________________________________________________________
Lessons from Mixing Rust and Java: Fast, Safe, and Practical
Author : killme2008
Score : 108 points
Date : 2025-05-15 02:18 UTC (3 days ago)
(HTM) web link (medium.com)
(TXT) w3m dump (medium.com)
| lukax wrote:
| Do not write the bindings manually. Just use the amazing uniffi-
| rs library from Mozilla.
|
| https://github.com/mozilla/uniffi-rs
|
| You can generate bindings for multiple languages. It supports
| error handling on both sides and latest versions also support
| native async integration.
|
| I've used it to reuse the same Rust engine in iOS and Android
| apps and write native UI.
|
| https://github.com/koofr/vault
| cadamsdotcom wrote:
| Thanks! Anywhere you hit issues?
| lukax wrote:
| Not really. But I didn't use async (was not supported yet
| when I started using it).
|
| Bindings were easy, everything else (building, linking,...)
| was a bit pain to setup because there no good examples.
| 4hg4ufxhy wrote:
| The bindings are inefficient doing excessive cloning. But if
| performance is not a concern then its fine.
| no_wizard wrote:
| Couldn't you manually edit after generating so you could
| thereby optimize the code as needed?
| invalidname wrote:
| Why didn't OP use Panama or the modern Java native APIs which are
| far better than JNI?
| cyberax wrote:
| Android?
| invalidname wrote:
| Does OP target Android?
| pjmlp wrote:
| Proving the point about Android being Google's J++, and
| Kotlin their C#.
| sgt wrote:
| Should be possible with Zig as well. Is this one up to date?
| https://github.com/zig-java/jui
| rusbus wrote:
| Another useful library in this space that allows you to avoid
| manually writing bindings is https://github.com/duchess-
| rs/duchess
| never_inline wrote:
| 1. The article seems kind-of shallow. I didn't see any concrete
| (qualitative or quantitative) remarks about the "fast" part. I
| don't doubt you have reasons to do this - but I expected some
| information on what component are you writing using Rust + JNI,
| and how it helped? Or is it just a demo?
|
| At some point, repeated calls into the JNI are counter-productive
| to performance, since the JIT can not optimize them. Pinning
| affecting garbage collection is another potential drawback if any
| of your rust calls are long lived. If we don't measure and just
| conclude "we are fast because rust is faster than Java, and we
| took average of both speeds", it's a disservice.
|
| 2. Also, I see unsafe for each call? I'd rather isolate this into
| a class / different file, since in JNI only few types of calls
| are possible. (method returning one of the primtive types, an
| object or `void`). This is the approach I took in dart jnigen.
| (Though there, the call is Dart -> Java, not Java -> Native
| language). unsafe {
| env.call_method_unchecked( java_logger,
| logger_method,
| ReturnType::Primitive(Primitive::Void),
| &[JValue::from(format_msg(record)).as_jni()] );
| }
|
| 3. I believe some details are missing here. What's native_add_one
| mapped to? And how is tokio futures awaited from Java? I believe
| that's the important part you should be presenting.
| public CompletableFuture<Integer> add_one(int x) {
| long futureId = native_add_one(x); // Call Rust
| return AsyncRegistry.take(futureId); // Get CompletableFuture
| }
|
| 4. Also please don't use ChatGPT for writing anything. It totally
| derails the reader by mentioning irrelevant details and long
| winded corporate conclusion at the end of every sentence.
| killme2008 wrote:
| This article summarizes our experience from a commercial
| project that runs on an in-vehicle Android system. In this
| project, we needed to invoke Rust code(DB) from Java(App), so
| we couldn't directly use the project's source code for
| demonstration. Instead, we created a demo project:
| https://github.com/GreptimeTeam/rust-java-demo
|
| 1. I agree that using Rust doesn't necessarily mean faster
| performance; it simply gives you the opportunity to implement
| some compute-intensive modules in Rust, which is a possible
| approach.
|
| 2. This is a great suggestion, and we organized our project in
| the same way. You don't need to use unsafe for every call.
| However, if you want to call JNI APIs from Rust, unsafe is
| required.
|
| 3. Sorry, some details were missing here. We use
| AsyncRegistry(Java) as an intermediary. Before initiating an
| async operation in Rust, we need to call Java code in advance
| to register a future and obtain a unique future ID. After the
| async execution completes, we retrieve the registered future by
| its ID, and then complete it or complete it exceptionally
| depending on the async result. You can refer to this code:
| https://github.com/GreptimeTeam/rust-java-demo/blob/90ffa0ba...
| and https://github.com/GreptimeTeam/rust-java-
| demo/blob/90ffa0ba...
|
| 4. This article was not generated by AI; it's just that our
| official blog has a fixed template at the end. Sorry for the
| inconvenience.
| never_inline wrote:
| Thanks for the clarifications. Good if you mention the
| background in the medium post. Otherwise it reads like a PoC
| demo.
|
| 5. How did you handle java local and global ref lifetimes in
| rust callee? Was it assumed that java caller owns all the
| refs and freed after the rust computation returns? Or did
| your calls mostly involve byte buffers and primitive types?
| That latter is a sweet spot but not always feasible.
| bullen wrote:
| I agree that Java + native is the way to go.
|
| But does rust really give you an edge over C/C++?
|
| Here is how you do JNI with C++: http://move.rupy.se/file/jvm.txt
|
| So simple it's ridiculous!
|
| Then you can use RegisterNatives to give C++ API to the Java side
| instead of the stub (Java calls C++ .dll/.so) thing...
| pjmlp wrote:
| Your comment is exactly the reason why while I find Rust a cool
| language, I would be using C++ instead.
|
| That is the systems language most JVM implementations make use
| of, alongside Java, and what is directly supported by JNI
| tooling, including on Java IDEs mixed language debugging.
|
| And in what concerns Java, _native_ is anyway the synonym for
| _unsafe_.
|
| However to each their own.
| tialaramex wrote:
| C++ is a perfectly good programming language if you never
| make mistakes. So the problem is that of course you'll make
| mistakes, and the people who wrote your compiler make
| mistakes, and the C++ committee makes mistakes, and the
| language's inventor makes mistakes, if you work in a team the
| other team members and the tooling both also have mistakes.
| Other than these issues, sound choice.
| pjmlp wrote:
| You can bash C++ as much as you feel like, if it makes you
| happy.
|
| I am well aware of the issues with C++'s flaws, sometimes
| it is easier to deal with such flaws, than adding a new
| layer into the C++ sandwich of debugging tools, build
| toolchain and IDE tooling for a given language runtime, and
| dragging a whole team for the ride as well.
|
| What about removing LLVM dependency from Rust, switch to
| Cranelift, so that rustc isn't hindered by C++ mistakes on
| LLVM?
| tialaramex wrote:
| Sure, or perhaps since LLVM devs like niches so much they
| might decide to RIIR.
|
| For me the big advantage of Cranelift isn't that it's
| written in Rust it's that they seem to have invested more
| into coherent semantics. I do not want to write code
| which is correct but is miscompiled because the compiler
| internal semantics are nonsense and that's a small but
| noticeable problem in LLVM.
|
| You can write contorted (but safe) Rust which LLVM just
| plainly miscompiles, the rust layer is like "Make local
| variable A, and local variable B" Ok says LLVM, "and now
| is the address of A the same as the address of B?" "No",
| says LLVM those are different, not the same variable so
| different addresses. OK says the Rust. Now, subtract the
| smaller from the larger and tell me the number you got.
| "Zero" triumphantly proclaims LLVM having deduced that we
| don't need A or B so there's no need to store them
| anywhere, but forgotten that it promised their addresses
| aren't the same... Oops.
|
| That example is silly, but it's hard to be sure how many
| large codebases might tickle equivalent LLVM
| miscompilation, which is not good.
| mrkeen wrote:
| Because those mistakes get fixed on LLVM devs' time, not
| on parent's.
| dbacar wrote:
| WHich part of the Java app was slow for you? And did you check on
| FFI (foreign function interface) ?
| aeonik wrote:
| I thought JNI was being deprecated in favor of the new FFM
| interface.
|
| https://openjdk.org/jeps/472
| cogman10 wrote:
| Notice the delivery version (24). That was literally just sent
| out in March. The FFM interface was stabilized in 22. 21 is the
| latest LTS version which is the furthest most orgs will go.
| da_chicken wrote:
| That doesn't mean you should happily go out and build a new
| cathedral of technical debt.
| immibis wrote:
| This says:
|
| > It is not a goal to deprecate JNI or to remove JNI from the
| Java Platform.
|
| It says they want to put a safety barrier in front of JNI, so
| you'll have to explicitly indicate that you want a module to
| use JNI.
| aeonik wrote:
| I know that's the goal, but I was doing deeper reading and
| didn't understand the nuances here. It _felt_ deprecated, and
| I remember reading that you should prefer FFM.
|
| That's one of the reasons I posted it. A lot of knowledgeable
| people here can chime in more details. And a sibling comment
| did!
| pron wrote:
| FFM is the recommended alternative, but JNI is not being
| deprecated. There are some things that JNI can do that FFM
| can't, in particular - initiating calls from native code to
| arbitrary Java methods. FFM only supports upcalls to Java in
| the form of Java callbacks that are passed to the native code.
| However, this is enough for the vast majority of Java-native
| interactions, so FFM should be preferred in new code (it's much
| easier to use than JNI).
| koakuma-chan wrote:
| I found mixing Bun and Rust works pretty well. Bun has gotten
| many cool new things recently which feel great to use and it has
| a nice FFI API. So I have Next.js apps running in Bun runtime and
| anything CPU bound is written in Rust and called via FFI.
| pjmlp wrote:
| I would have expected Zig, given Bun, or using Deno instead, if
| Rust as extension language.
| koakuma-chan wrote:
| > I would have expected Zig
|
| You can use Zig instead if it meets your needs, but last time
| I checked, the Zig ecosystem was lacking compared to Rust.
|
| > or using Deno instead, if Rust as extension language
|
| Deno? There was a thread the other day that Deno is dead. I
| wouldn't even compare Deno and Bun because Bun is just waaay
| better. Even before Bun already felt very polished and nice
| to use, and now it even has native postgres and s3 clients.
| And it doesn't try to sell you a KV database or some shit.
| pjmlp wrote:
| The point I was making was using the same language the VM
| makes use of, instead of adding extra layers.
| koakuma-chan wrote:
| I mean, I guess there would be some point if you, e.g.,
| forked bun and wrote new APIs in Zig. But if you are
| using FFI it doesn't really matter because the interface
| is C ABI anyway; there's no extra layers in using a
| language different from the one your runtime uses.
| never_inline wrote:
| JS developer use tooling older than 3 years challenge. Level:
| impossible.
| koakuma-chan wrote:
| It's not only JS. The tooling was just trash everywhere
| before Rust started gaining traction. E.g. for Python there
| is uv now, and I am definitely not going back to freaking
| pip.
| nilslice wrote:
| Others should consider Chicory, enabling JVM apps to run wasm
| code (compiled from Rust or many other languages) at high speed,
| securely in-process.
|
| JNI introduces a lot of other challenges and sacrifice in terms
| of portability, memory and retaining observability and control
| within the JVM.
|
| Chicory even supports AOT compilation to translate wasm bytecode
| to JVM bytecode for native performance.
|
| https://github.com/dylibso/chicory
___________________________________________________________________
(page generated 2025-05-18 23:01 UTC)