Context: exploring how feasible it is to write a Postgres Extension with Clojure + native-image. This would (very likely) require exposing Clojure/Java/JVM code to be called from C. My current understanding is that this is one way to achieve this https://www.graalvm.org/latest/reference-manual/native-image/guides/build-native-shared-library/ Has anyone done/attempted this – exposing native-image/Clojure code to be called from C? If yes, were there any major gotchas or potential roadblocks?
@raspasov I'm actually doing exactly this, made a series of 3 posts with what I found (because the GraalVM's documentation is atrocious): Part 1 - https://mauricio.szabo.link/blog/2025/04/14/exposing-clojure-to-ruby-and-other-languages/ Part 2 - https://mauricio.szabo.link/blog/2025/04/21/exposing-clojure-to-ruby-and-other-languages-callbacks/ Part 3 - https://mauricio.szabo.link/blog/2025/04/22/exposing-clojure-to-ruby-and-other-languages-java-objects-in-c/
Thank you! It’s on my reading list.
Any question, you can DM me (or post it here and mark me 🙂 )
https://cnuernber.github.io/dtype-next/tech.v3.datatype.ffi.graalvm.html
No experience with this particular approach, but it is part of the excellent dtype-next library that powers both libpython-clj and tech.ml.dataset.
I also found this illuminating spreadsheet https://docs.google.com/spreadsheets/d/1ViLHNUgrO2osh2AH0h7MaCaXz8g0UpLbyWojY5f10kk/edit?gid=332155605#gid=332155605 which explores various options
That spreadsheet is largely oriented at consuming native code from the JVM. It sounds like you're trying to go the other direction (consuming clojure/java from native code). I second @afoltzm dtype.next's recommendation. If you're interfacing with native code, it does the annoying bytecode generation for you and also has a bunch of utility functions for working with data on the heap that are useful. I used the basic approach of creating a native library using graalvm that can be called from native code for grease. I believe this is what https://github.com/babashka/sci/blob/master/doc/libsci.md does as well (although I don't think they use dtype-next).
> If yes, were there any major gotchas or potential roadblocks? As long as you're targeting platforms that are supported by native image, then I don't think there are any major roadblocks. I think the main tradeoffs compared to writing a similar library in c, c++, rust, or similar: • binary sizes will probably be larger • you'll be including a garbage collector • larger memory footprint You will also have to be more thoughtful about memory lifetimes for any data that is crossing the native boundary, but there are pretty good tools for dealing with that. Having said that, I think writing clojure code is much more productive than the native options so the tradeoffs can be worth it for some use cases.
@smith.adriane re: spreadsheet yes… and yes, that’s correct, the other direction (calling clojure from native) although I assume both ways will be necessary at some point. Thanks for the tips/overview!
> • you’ll be including a garbage collector I am aware of this trade-off and it’s probably my biggest fundamental question mark for this approach (outside of misc. annoyances).
I think native-image is the only option that let's you create a shared or static native library that can be called from native code.
I’ve heard the native-image GC is worse than the JVM one, but that’s all anecdotal/internet chat.
My impression was that the GC just made different tradeoffs, but I haven't had any issues so I haven't looked into it at all.
Yeah I assume much depends on how much garbage “pressure” is generated… And I think the newest “effectively” pause-less JVM GCs are not available in native-image.
Oh, another caveat worth mentioning that you're probably already aware of is that not every clojure library is native-image compatible (although many are with little to no tweaks). Fortunately, many useful libraries have already been made native-image compatible through the work on babashka.
Ah, yes… Do you know what kind of libraries would not be supported?
I am typically pretty conservative in my library choice, esp. around things that do funky dynamic arguably non-idiomatic Clojure stuff (say around… vars/namespaces)
Not gonna name a lib 😅
Most of the libraries I’d use are pure functions only
I believe the main troubles come from: • java interop that has reflection warnings • libraries that use native libraries
pure clojure libraries are usually fine if I remember correctly.
> • java interop that has reflection warnings So I assume that would be “relatively” easy to fix in each library?
You can also run into trouble if a library does stuff when you run all the top level forms in a namespace (eg. start threads, read resources)
> • libraries that use native libraries I see how that would be an issue but I typically don’t use many of those, outside of dtype next etc…
> You can also run into trouble if a library does stuff when you run all the top level forms in a namespace (eg. start threads, read resources) That’s good to be aware of, thanks! I think most libs don’t do that but good to keep in mind
https://github.com/clj-easy/graalvm-clojure - that’s a good list of libs