You cannot call Rust directly from Ruby, Java, or C# because they run on different runtimes; instead, you must compile Rust into a shared library (.so, .dll, or .dylib) using the #[no_mangle] and extern "C" attributes, then load that library via each language's Foreign Function Interface (FFI) bindings.
For Ruby, use the ffi gem to load the library and map C functions. For Java, use the Java Native Interface (JNI) or a tool like jnr-ffi. For C#, use DllImport with System.Runtime.InteropServices. The key is ensuring your Rust code exports C-compatible function signatures and handles memory safely, often by passing raw pointers or using Vec converted to slices.
Here is a minimal Rust library (lib.rs) that works for all three:
// Cargo.toml: [lib] crate-type = ["cdylib"]
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn get_greeting() -> *const i8 {
static MSG: &str = "Hello from Rust";
MSG.as_ptr()
}
Compile it with cargo build --release to generate the shared object in target/release/.
Ruby Example:
require 'ffi'
module RustLib
extend FFI::Library
ffi_lib File.join(__dir__, 'target/release/libmy_rust_lib.so') # Adjust path/ext
attach_function :add_numbers, [:int32, :int32], :int32
end
puts RustLib.add_numbers(10, 20) # => 30
Java Example (using JNR-FFI):
import com.github.jnr.ffi.LibraryLoader;
import com.github.jnr.ffi.annotations.Name;
public interface RustLib {
RustLib INSTANCE = LibraryLoader.create(RustLib.class).load("my_rust_lib"); // Adjust name
@Name("add_numbers")
int addNumbers(int a, int b);
}
public class Main {
public static void main(String[] args) {
System.out.println(RustLib.INSTANCE.addNumbers(10, 20)); // => 30
}
}
C# Example:
using System.Runtime.InteropServices;
class Program {
[DllImport("my_rust_lib", CallingConvention = CallingConvention.Cdecl)]
public static extern int add_numbers(int a, int b);
static void Main() {
System.Console.WriteLine(add_numbers(10, 20)); // => 30
}
}
Critical Notes:
- Memory Management: Never return Rust objects or strings directly. For strings, return a
*const i8(C string) and let the host language manage the lifetime, or pass a buffer pointer into Rust to fill. - Error Handling: Rust
Resulttypes do not map to exceptions in C. Return error codes (integers) or use a global error state variable. - Threading: Ensure your Rust library is thread-safe if the host language calls it from multiple threads. Use
#[no_mangle]strictly for C ABI compatibility.