[nix-shell:~/dev/roc/examples]$ cd ruby-interop/ && ../../target/release/roc build --lib
0 errors and 0 warnings found in 194 ms while successfully building:
libhello
[nix-shell:~/dev/roc/examples/ruby-interop]$ ruby extconf.rb
checking for -lhello... no
creating extconf.h
creating Makefile
[nix-shell:~/dev/roc/examples/ruby-interop]$ make
compiling demo.c
linking shared-object demo.so
[nix-shell:~/dev/roc/examples/ruby-interop]$ irb
irb(main):001:0> require_relative 'demo'
Traceback (most recent call last):
5: from /nix/store/2xvc6i6dnar93p1wvh335jjsgmmjqg5c-ruby-2.7.7/bin/irb:23:in `<main>'
4: from /nix/store/2xvc6i6dnar93p1wvh335jjsgmmjqg5c-ruby-2.7.7/bin/irb:23:in `load'
3: from /nix/store/2xvc6i6dnar93p1wvh335jjsgmmjqg5c-ruby-2.7.7/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
2: from (irb):1
1: from (irb):1:in `require_relative'
LoadError (/home/dankey/dev/roc/examples/ruby-interop/demo.so: undefined symbol: roc__mainForHost_1_exposed_generic - /home/dankey/dev/roc/examples/ruby-interop/demo.so)
I think i did everything right?
ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
clang version 13.0.1
GNU Make 4.3
i actually am not sure how that's supposed to work
both host.c and demo.c are declaring an external roc__mainForHost_1_exposed_generic
, but who implements it? :thinking:
The Roc app implements it. In the Roc code it would just be called mainForHost
hm, so it looks like extconf.rb
isn't finding libhello
when I run it, I get this:
$ ruby extconf.rb
checking for -lhello... yes
creating extconf.h
creating Makefile
so yes
instead of no
after checking for -lhello
admittedly I haven't tried it on NixOS
so maybe extconf.rb
is passing -lhello
to clang
or ld
or something, and on NixOS it doesn't look in the current directory for a libhello
?
or maybe it needs a different file extension on libhello
?
Richard Feldman said:
or maybe it needs a different file extension on
libhello
?
yea that's what i suspected. there's libhello.so.1.0
. not sure how to validate that that's what wrong
also it might be a nixos thing, yes
BTW full discretion I actually barely know ruby at all, but I figured if i can get it working and understand how it works, I might be able to replicate your work for a python-interop, which shouldn't be that hard
in the meantime I'll maybe test on my debian machine to see if it actually is just a nixos thing
also changing the extension to libhello.so etc doesn't make a difference ruby still doesn't get it
i figured maybe playing with dir_config
would solve it but doesnt seem so
(even creating a lib dir and moving libhello.so there, weird stuff)
doesn't work on debian either
(as in nixos it says no after checking for -lhello)
I assume @Richard Feldman you're using macos?
yup, interesting
one of my coworkers get it working at NoRedInk on Linux, but I forget what the trick was
I think it had to do with file extension though - did you try libhello.so.1
?
cc @Juliano
Richard Feldman said:
I think it had to do with file extension though - did you try
libhello.so.1
?
yes. no change
i don't remember hitting this mainForHost
problem specifically
lemme look at our setup to see what extra we got compared to dank's stuff
we had to ln -sf libhello.so.1.0 libhello.so.1
and ln -sf libhello.so.1 libhello.so
, else demo.so
wouldn't be properly linked (different error tho)
On Linux, for this to work we also needed to set either LD_LIBRARY_PATH
to this directory or LD_PRELOAD
to the absolute path of libhello.so
i think patchelf could've helped avoid LD_LIBRARY_PATH
stuff, but i was lazy
it might be some difference in demo.c
? also we're using a pretty old version of roc (3-4mo old?)
this is my demo.c https://gist.github.com/omnibs/e6c09b8fee711549d7009e234c34f438
might also be something with your roc lib's platform? our PoC used this: https://gist.github.com/omnibs/8cd4eedae5f4c645fc857c86bc213d6a
then you use it in your lib's main.roc
like so:
app "libhello"
packages { pf: "platform/main.roc" }
imports []
provides [makeItRoc] to pf
makeItRoc : Str -> Str
makeItRoc = \str ->
if Str.isEmpty str then
"I need a string here!"
else
"\(str), OH YEAH!!! 🤘🤘"
the other stuff mentioned didnt seem to make a difference but
[nix-shell:~/dev/roc/examples/ruby-interop]$ LD_PRELOAD="$(pwd)/libhello.so.1.0" ruby extconf.rb
ruby: symbol lookup error: /home/dankey/dev/roc/examples/ruby-interop/libhello.so.1.0: undefined symbol: roc_alloc
good news bad news
bad news is that the ruby-interop doesn't work and idk how to make it work
good news:
gottem
haha. cool
yooooo awesome!!!
want to make a PR to add that to examples
?
that would be our second dynamic language interop example ever!
Wow, this is very cool!!
Richard Feldman said:
want to make a PR to add that to
examples
?
PR's up :)
image.png
why does it say it like that
sounds so rude lol
in any case @Richard Feldman I added zulip link + some readme cleanup bc it seemed not great + some key notes on the demo.c implementation
hope that's better
looks great, thank you - approved!
the ruby interop example is/was broken. I'm not sure it ever really worked
it definitely did on a M1 Mac (I haven't tried recently though; it may have regressed), and Juliano and I used it to get Ruby interop working on NixOS
good thing I stumbled upon that ruby-interop example though
image.png
I'm no expert, but I'd say that's pretty dank
Lots of languages can speak c. Though some may make it hard to deal with more complex roc types
yoooo that's awesome!!!
is that using JNI?
yes indeed
gotta say JNI is much nicer to work with than I anticipated
actually panama might be better for us
cause jextract can auto generate the java glue
meaning the user wouldn't need to write any java (including the static load and native method decls)
so that he could, from any jvm lang at all (be it clojure scala or whatnot), build the deps and import functions from the java glue
also that would make it work for jshell
bc jshell and JNI do not play nice as it turns out
interesting! Would any of the JNI alternatives require a third-party dependency?
when it comes to interop, I generally try to avoid third-party deps if possible
damn i thought it wouldn't but it seems that openjdk, even 19 only contains only the api for it,
and the jextract binary ships separately
so it is kinda third party dep https://jdk.java.net/panama/
btw are you ok with having https://github.com/sheredom/json.h ?
java doesn't have a json parser
yeah seems reasonable as a starting point
eventually JSON shouldn't be necessary, it's just a really convenient way to get interop with a given language up and running :big_smile:
you mean for an examples/
entry, yeah?
yea
yeah sounds good!
just letting u know i haven't forgot about the jvm-interop, just ran into some issues trying to generalize to langs like clojure
right now kinda waiting for the headless release to come out so my mate who's a jvm god will help smooth out some rough jvm edges
nice, thanks for the update - looking forward to it! :smiley:
What OS is your friend using @dank? I'll make sure to test the headless release on that OS once it's ready
debian
hello hello
i think jvm-interop is ready
https://github.com/dankeyy/roc/tree/jvm-interop/examples/jvm-interop
tested on java kotlin and scala
couldn't get clojure to work because its import system is weird.
people suggested all kinda import tools and deps which i didn't want to dive into.
if anyone here though got a simple solution that'd be great
also i kinda ditched the json encoding part, hope that's ok (?)
Was kinda trivial to just grab the bytes, but tell me if that's bad somehow and we can switch back
also have to say jvm Strings are an absolute meme https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542
spent way too long trying to find a way around it that wouldn't require rewriting utf8 lmao, but i did find a pretty nice simple solution which is to convert it first into a java byte array, then to string. by that "outsourcing" the conversion to the jvm
so yea pretty fun. tell me what you think, and ill pr if it's ready
dank said:
also i kinda ditched the json encoding part, hope that's ok (?)
yeah totally! That was just a way to quickly get a more useful proof-of-concept than just sending (for example) strings or bytes across, without having to go as far as to write translations for all the target language's datatypes :sweat_smile:
of course if you want a fully-featured interop, you'd need to actually do that (e.g. in the Ruby one, converting not just Ruby strings to/from Roc strings, but also Roc Lists to Ruby Arrays...etc)
but you gotta start somewhere, and these examples that just demonstrate the interop with one datatype are still super useful!
that totally looks ready to PR to me
wow, I'm impressed by how little code it takes on the JVM side
I was expecting a lot more!
yeaa, was really surprised by jni here
also in the beginning the bridge function was more involved but i managed to cut some stuff out which was great
Richard Feldman said:
but you gotta start somewhere, and these examples that just demonstrate the interop with one datatype are still super useful!
yea it'd be nice if we'd have that but i feel like a break from jvm lol
so i say we pr it and if anyone here want to interop arrays too, build on it !
yeah totally!
happy to say clojure example works aswell
turns out in clojure u need to mention the path like so clj -Sdeps '{:paths ["."]}'
ooo nice!
so i wanna make the interops more complete and i have a couple questions
what do you think are compelling function examples for interops?
needs to be cool, but not too complex as to not distract from the subject
something like take this java array + scalar and return a new java array with all elements multipled by the scalar?
to pass the platform a List <N>, I need a RocBytes+init_rocbytes of appropriate size, right?
but what if i have a couple of those
like i want List U8 and also List U32
RocBytes can only have one definition :thinking:
oh wait i might be dense
The "RocBytes" name isn't demanded by the platform
yea nvm about the second point then
I can just make RocBytesN structs and populate them however i want from roc side, i think
i also understand host.c serves no purpose in my impl. kinda carried over from ruby interop (looking back im not even sure why it has a main)
like all of the necessary logic is in bridge.c
@Richard Feldman you know what im saying?
and if no decoding is necessary, then the indirection between platform/main.roc and main.roc is redundant (all the platform/main.roc) does it call the app's function
rather it's not that the platform is redundant
it's that the app isn't really an app, it just serves like a proxy here
it might just be that a language interop is a special case but it seems to me that here, all logic is platform logic
to make a long story short i feel like all that is needed here is one .c file and one .roc file
Last updated: Jul 05 2025 at 12:14 UTC