I was working on a Rust project where we needed to interact with code written in C.
I had to learn how to work with FFI (Foreign Function Interface) in Rust and wrote up this little guide for others.
This repository is a working example of the final code from the tutorial I wrote below. Clone it and run it using cargo run
.
$ cargo run
Compiling rust-ffi-to-c v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.93s
Running `target/debug/rust-ffi-to-c`
[Rust] Hello from Rust! 🦀
[Rust] Calling function in C..
[C] Hello from C!
[C] Input a is: 5000
[C] Input b is: 5
[C] Multiplying and returning result to Rust..
[Rust] Result: 25000
We use extern
to reference the multiply()
function, which is written in C (src/multiply.c
).
In this case we want to multiply integers, so we import a C-compatible integer type into Rust from core:ffi
. (See all the available types)
We then define the argument types and return type for our C function as c_int
(equivalent to i32
in Rust).
extern crate core;
use core::ffi::c_int;
extern "C" {
fn multiply(a: c_int, b: c_int) -> c_int;
}
Any use of foreign function is considered unsafe because the Rust compiler can't guarantee memory safety in foreign code.
So in our main Rust file (src/main.rs
) we call the function in an unsafe
block, then pass in two i32
integers, and print the result.
unsafe {
println!("Result: {}", multiply(5000, 5));
}
First we compile our multiply.c
file using a C compiler:
clang src/multiply.c -c
The -c
flag tells the C compiler to output a "object file (.o
)" instead of an executable program. So it creates a multiply.o
file that we can use as a shared dynamic library in our Rust code.
Second we create a static library file libmultiply.a using the ar tool:
ar rcs libmultiply.a multiply.o
Then we compile our program using the Rust compiler:
rustc src/main.rs -l multiply -L .
The -l multiply
option tells the Rust compiler to link the shared library.
The -L .
option tells the Rust compiler to look for libraries in the current directory.
The compiler creates an executable named main
which we can run:
./main
[Rust] Hello from Rust! 🦀
[Rust] Calling function in C..
[C] Hello from C!
[C] Input a is: 5000
[C] Input b is: 5
[C] Multiplying and returning result to Rust..
[Rust] Result: 25000
It gets tedious to compile the files manually every time, so we will use cargo build script and the cc
crate to automate this process.
Add cc
to the projects build dependencies:
[build-dependencies]
cc = "1.0"
Create a build.rs
and add compile instructions:
extern crate cc;
fn main() {
cc::Build::new().file("src/multiply.c").compile("multiply");
}
And now we can use Cargo to build both the C and Rust code and run the program:
cargo run
-
From Rust 1.64.0 it is now recommended to use
core::ffi
instead ofstd::os::raw
to access C types. The latter is now an alias to types in thecore::ffi
module.core
is also available in places where the Rust standard library (std
) is not, like embedded projects. -
Mapping out functions manully using
extern
is fine for small projects, but as soon as you are dealing with a bigger library or codebase, you want to take a look atbindgen
. It can automatically generate the bindings for C or C++ libraries, making using them in Rust a lot easier. See thebindgen
User Guide. -
We can control how our code is linked using the
#[link()]
attribute.. It allows us to specify or rename functions and change the type of linking to use, eg. to static:#[link(name = "multiply", kind = "static")] extern "C" { // ... }
-
FFI chapter in The Rustonomicon book (Rustonomicon is the official guide to unsafe Rust)
-
"A little C with your Rust" chapter in The Embedded Rust Book (The official Embedded Rust guide)
-
📖 Chapter 11: "Foreign Function Interfaces" in Rust for Rustaceans by Jon Gjengset
-
📖 Chapter 23: "Foreign Functions" in Programming Rust, 2nd Edition by Jim Blandy, Jason Orendorff & Leonora F. S. Tindall