Tool to use to create bindings for multiple languages

I was eager to dive into the world of creating bindings for my Rust crate. After some extensive research, I came across three prominent binding generators:

  1. Interoptopus: Offers support for C#, C, Python, and even allows you to create custom language bindings.
  2. Diplomat: Provides bindings for C, C++, and JavaScript.
  3. Uniffi: Offers virtually limitless options for target languages.

As I delved deeper into my research, it became clear that Diplomat is the most straight-forward and easy-to-use choice among the three binding generators. However, it does have certain limitations. Interoptopus, on the other hand, allows you to write a custom backend, essentially providing limitless possibilities for target languages (for which one needs to have the skills to do so) . Uniffi offers no limitations but requires manual FFI (Foreign Function Interface) creation, which can be impractical for larger projects unless there is an automated generation process.

The challenge I encountered is that individual crates providing bindings for specific languages aren't always compatible with each other, and some may not support all Rust features.

Given my lack of prior experience with bindings, and my desire to create bindings for popular languages such as Kotlin, Python, JavaScript, C#, and potentially more, I'm looking for the most efficient tool or method to achieve this. What would you recommend?

Only Rust supports all Rust features. You won't be able to make idiomatic Rust code look idiomatic in JavaScript or Python or anything else.

1 Like

I have not used any of the three you mentioned above, but here are a few pieces of advice when working with FFI:

  • Develop a C API first. It is most similar to Rust (easiest to make), and many other languages can use C bindings too (which may relieve work somewhat)
  • Check out cbindgen to automatically create C headers for Rust projects. I also recommend looking at cxx, is meant to ease FFI with C++
  • Work on bindings for languages that you will use first, since you will test them more and stay motivated on the project
3 Likes

UniFFI dev & maintainer here.

I'm not sure what you mean here. UniFFI is a bindings generator after all.
You define your API, either in UDL or newer using macros, and UniFFI takes care of generating the FFI layer in between and the conversions on both sides (Rust to the FFI and the FFI to your target language).
Creating bindings to new languages is a bit of effort, but not impossible (we have contributors who have written Go and C# bindings afterall and we maintain our own connections to Firefox's JS engine).

Oh during my research about uniffi I didn't find any information about the macros generation ,.

Btw: After some more digging I found Motivation - The UniFFI user guide which gives steps on building and auto generation of the FFI. Which ill try and see if it works

So I should then create the C headers then link the C into the target language (kotlin for example)

I admit our docs really need a coordinate effort to update them.
Macros are documented here: Procedural Macros: Attributes and Derives - The UniFFI user guide
But even without that you define your API in UDL and uniffi takes care of generating the low-level stuff in between. No need to manually write the hard FFI parts.

You have a few options. Though after seeing what others have been saying about UniFFI, I recommend probably using that instead.

When doing it manually (without bindgens / UniFFI), no matter what you'll do you will have to re-create your API interface for every single language. The only difference is how you do it. For instance, in Python you can use the ctypes module to load functions from a dynamic library. In this case you would need C bindings first.

On the other hand, you could easily use pyo3 to make Python bindings. Instead of manually grabbing the symbol pointer like in ctypes, you can literally just import the dynamic library and use the functions normally.

It all depends on which side you want to make the bindings from. C bindings is about creating them from the Rust side. pyo3 is the same thing, but for Python. ctypes is from the other side, where you create the bindings in Python.

This is all pretty confusing and complicated, which is why I highly recommend using UniFFI or some other FFI library.

I began using the macros of uniffi , I have gotten the correct command to run it
cargo run --bin bindgen generate --library target\release\musixmatch.dll --language kotlin --out-dir out
However when I try doing :

use uniffi::*;

#[derive(Object)] 
pub struct MusixAbgleich<'a> {
   // client : Client,
    api_key : &'a str, 
 //   error_resolver : &'a (dyn Fn(RequestError<Value>) + Sync + Send)
}

I get error :

error[E0726]: implicit elided lifetime not allowed here
  --> src\client.rs:44:12
   |
44 | pub struct MusixAbgleich<'a> {
   |            ^^^^^^^^^^^^^ expected lifetime parameter
   |
help: indicate the anonymous lifetime
   |
44 | pub struct MusixAbgleich<'_><'a> {
   |                         ++++

Which I know is fully wrong but then if I add the suggestion I get :

error[E0637]: `'_` cannot be used here
  --> src\client.rs:44:29
   |   
44 | pub struct MusixAbgleich<'a,'_> {    
   |                             ^^ `'_` is a reserved lifetime name

Is there a way to solve or work around this?

Lifetimes will not work. It's just not a concept that maps to the foreign languages UniFFI targets, so we can't support that.
(I'll see that I get a bug filed for that though, maybe we can catch that in the macro and provide a better error message)

1 Like

Oh thats unfornuate that lifetimes won't work , and yes the improvement of the report will be useful

After some toying around and removing any lifetimes like 'a I got to this defination :

#[derive(uniffi::Object)] 
pub struct MusixAbgleich<F> where F : Fn(RequestError<Value>) + Sync + Send {
    client : Client,
    api_key : &'static str, 
    error_resolver : F 
}

However on cargo build I get :

error[E0107]: missing generics for struct `MusixAbgleich`
  --> src\client.rs:42:12
   |
42 | pub struct MusixAbgleich<F> where F : Fn(RequestError<Value>) + Sync + Send {     
   |            ^^^^^^^^^^^^^ expected 1 generic argument
   |
note: struct defined here, with 1 generic parameter: `F`
  --> src\client.rs:42:12
   |
42 | pub struct MusixAbgleich<F> where F : Fn(RequestError<Value>) + Sync + Send {     
   |            ^^^^^^^^^^^^^ -
help: add missing generic argument
   |
42 | pub struct MusixAbgleich<F><F> where F : Fn(RequestError<Value>) + Sync + Send {  
   |                         +++

Am I making some mistake or can uniffi not handle generics like above as well? And now I am also getting (fyi this worked before)

error[E0433]: failed to resolve: could not find `setup_scaffolding` in `uniffi`
  --> src\lib.rs:29:9
   |
29 | uniffi::setup_scaffolding!();
   |         ^^^^^^^^^^^^^^^^^ could not find `setup_scaffolding` in `uniffi`

For more information about this error, try `rustc --explain E0433`.

Where

#![doc = include_str!("../README.md")]

#![forbid(....)]

#![deny(missing_docs,unused_results)]

uniffi::setup_scaffolding!();

This is my dependency for uniffi :


# For bindings
uniffi = { version = "0.24.3" , features = ["cli"]}

So am I doing something wrong?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.