Rust FFI (Foreign Function Interface)

What is Rust FFI?

The Foreign Function Interface (FFI) enables Rust programs to call functions or access data written in other languages. The most common scenario is calling C functions since C is widely used for system-level programming and has numerous libraries.

Key benefits of using FFI in Rust:

  1. Reuse: Utilize existing libraries written in other languages.
  2. Optimization: Write performance-critical parts in languages like C or C++.
  3. Interoperability: Combine Rust’s safety with the flexibility of other languages.

How Does FFI Work in Rust?

Rust provides the extern keyword to define functions and types from other languages. To interact with these, Rust relies on the C ABI (Application Binary Interface) for compatibility.

Setting Up FFI in Rust

Step 1: Create a Rust Project

Create a new Rust project:

cargo new rust-ffi-example
cd rust-ffi-example

Step 2: Create or Add C Code

Write a simple C file named example.c with the following content:

// example.c
#include <stdio.h>

void hello_from_c() {
printf("Hello from C!\n");
}

int add_numbers(int a, int b) {
return a + b;
}

Step 3: Compile the C Code

Compile the C file into a shared library:

gcc -shared -o libexample.so -fPIC example.c

Using FFI in Rust

1. Declaring External Functions

In Rust, use the extern block to declare functions available in the shared library.

#[link(name = "example")] // Links the shared library "libexample.so"
extern "C" {
fn hello_from_c();
fn add_numbers(a: i32, b: i32) -> i32;
}

2. Calling External Functions

Use the unsafe block to call external functions because Rust cannot guarantee their safety.

fn main() {
unsafe {
hello_from_c(); // Call the C function
let result = add_numbers(5, 7); // Call the C function
println!("Result from C: {}", result);
}
}

Complete Example: Combining Rust and C

Here’s the complete Rust code to call the C functions:

#[link(name = "example")]
extern "C" {
fn hello_from_c();
fn add_numbers(a: i32, b: i32) -> i32;
}

fn main() {
unsafe {
hello_from_c();
let result = add_numbers(10, 20);
println!("Sum from C function: {}", result);
}
}

Output

After running the program:

Hello from C!
Sum from C function: 30

Handling Complex Data Types

For advanced scenarios, you might need to pass or return pointers, structs or arrays between Rust and C. Let’s look at some examples:

Passing Strings

Strings need to be converted to C-compatible CStr or CString.

use std::ffi::CString;

#[link(name = "example")]
extern "C" {
fn print_message(message: *const i8);
}

fn main() {
let message = CString::new("Hello from Rust!").expect("CString::new failed");
unsafe {
print_message(message.as_ptr());
}
}

Working with Structs

If your C code uses structs, you can define equivalent structs in Rust.

C Code:

struct Point {
int x;
int y;
};

int sum_point(struct Point p) {
return p.x + p.y;
}

Rust Code:

#[repr(C)] // Ensures the Rust struct matches the C layout
struct Point {
x: i32,
y: i32,
}

#[link(name = "example")]
extern "C" {
fn sum_point(p: Point) -> i32;
}

fn main() {
let point = Point { x: 10, y: 20 };
unsafe {
let result = sum_point(point);
println!("Sum of point: {}", result);
}
}

Calling Rust Code from C

Rust can also expose functions to be called from C. This is useful when integrating Rust into existing C projects.

Exposing a Rust Function

Add the following to your Rust code:

#[no_mangle] // Prevents Rust from mangling the function name
pub extern "C" fn hello_from_rust() {
println!("Hello from Rust!");
}

Compiling as a Shared Library

Update Cargo.toml to compile as a library:

[lib]
crate-type = ["cdylib"]

Compile the Rust code:

cargo build --release

Use the generated libyour_project_name.so in your C project.

Leave a Comment

BoxofLearn