What is Rust FFI?
When we write software in Rust, we want to use powerful existing libraries written in other languages like C or C++. Instead of rewriting everything form starting, Rust provides a mechanism to directly talk to other programming languages. This is known as FFI(Foreign Function Interface).
In simple terms, Rust FFI is a bridge that allows Rust code to call functions, use data, or interact with code written in another language and vice versa. It’s one of the most powerful features of Rust, especially for systems programming, embedded development, and high-performance applications.
Why Do We Need FFI?
Imagine you are building a high-performance application in Rust but want to use a mature C library for image processing. Instead of rewriting that library in Rust, you can call it directly using FFI.
This will helps you to:
- Reuse existing code – Use well-tested libraries without duplicating effort.
- Boost performance – Write performance-critical sections in C/C++ and use Rust for safety.
- Improve interoperability – Combine Rust’s memory safety and security with the flexibility of other ecosystems.
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.
How To Setting Up FFI in Rust
First, we need to create a Rust project with the following command:
cargo new rust-ffi-example
cd rust-ffi-example
Then Calling a C Function from Rust
Let’s say we have a simple C function in a file called math.c:
// math.c
#include <stdio.h>
int add_numbers(int a, int b) {
return a + b;
}
Now we want to use this function in Rust. Here’s how we do it:
Declare the foreign function in Rust:
// main.rs
#[link(name = "math")] // tells Rust to link with math.c (compiled as libmath.a or libmath.so)
extern "C" {
fn add_numbers(a: i32, b: i32) -> i32;
}
fn main() {
unsafe {
let result = add_numbers(10, 20);
println!("The sum is: {}", result);
}
}
Compile the C code and run the Rust program:
gcc -c math.c -o math.o
ar rcs libmath.a math.o
cargo run
Output:
The sum is: 30
- extern “C” tells Rust the function follows the C calling convention, so Rust knows how to call it.
- unsafe: Since Rust can’t guarantee the memory safety of external code, you must mark FFI calls as unsafe.
- Linking: Use #[link(name = “…”)] to tell the compiler which external library to connect with.
Complete Example: Combining Rust and C
We can write low-level logic in C (like math, hardware control, or OS-level code) and then use it safely in our Rust program.
Below is a complete example where:
- We write a simple C library with two functions.
- We call those functions directly from a Rust program.
Create the C Code
First, let’s create a file named example.c:
// example.c
#include <stdio.h>
// A simple function that prints a message
void hello_from_c() {
printf("Hello from C!\n");
}
// A function that adds two numbers and returns the result
int add_numbers(int a, int b) {
return a + b;
}
This is a normal C code. It contains:
- hello_from_c() → prints a message to the console.
- add_numbers() → adds two integers and returns the result.
Compile the C Code into a Library
We need to compile this C file so Rust can link and use it:
gcc -c example.c -o example.o
ar rcs libexample.a example.o
Write the Rust Code
Now, let’s write our Rust program in main.rs:
// main.rs
// This tells Rust that we want to use a C library named "example"
#[link(name = "example")]
// Declare the C functions we want to use
extern "C" {
fn hello_from_c();
fn add_numbers(a: i32, b: i32) -> i32;
}
fn main() {
unsafe {
// Call the C function to print a message
hello_from_c();
// Call the C function to add two numbers
let result = add_numbers(10, 20);
println!("Sum from C function: {}", result);
}
}
- #[link(name = “example”)] tells Rust to link with the libexample.a file we created.
- extern “C” tells the compiler these functions use the C calling convention, so Rust knows how to call them.
Run the Program Using Cargo Run Command
Output:
Hello from C!
Sum from C function: 30
Handling Complex Data Types
In real-world projects, you often need to pass more complex data between Rust and C, like strings, structs, arrays, or pointers.
Rust can do all of that using the Foreign Function Interface (FFI). Let’s explore the two most common examples: strings and structs.
Passing Strings from Rust to C
In C, strings are represented as null-terminated character arrays (char*). Strings are stored as String or &str, which are not directly compatible with C.
Sending a String to a C Function
C Code (example.c):
#include <stdio.h>
void print_message(const char *message) {
printf("C received: %s\n", message);
}
This function expects a pointer to a character array (const char*), a standard C string.
Rust Code (main.rs):
use std::ffi::CString;
#[link(name = "example")] // Link to our C library
extern "C" {
fn print_message(message: *const i8);
}
fn main() {
// Create a C-compatible string
let message = CString::new("Hello from Rust!").expect("Failed to create CString");
unsafe {
// Pass the C string pointer to the C function
print_message(message.as_ptr());
}
}
Output:
C received: Hello from Rust!