Vanja Ćosić

How to call a C function from Rust

Edit: Thanks to the This Week In Rust newsletter for including this guide in issue #463 🙌

Recently 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 based on my notes. Maybe it will help someone else.

Final result #

I initially made a repository on GitHub with the example code and then wrote up this tutorial to go along with it. To try it, clone the repository and run it using cargo run.

This is the resulting output:

Terminal
$ 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

Tutorial #

1. Define an external function #

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).

main.rs · Rust
1
2
3
4
5
6
extern crate core;
use core::ffi::c_int;

extern "C" {
    fn multiply(a: c_int, b: c_int) -> c_int;
}

2. Call the C function from Rust #

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.

main.rs · Rust
11
12
13
unsafe {
    println!("Result: {}", multiply(5000, 5));
}

3. Compile and run #

First we compile our multiply.c file using a C compiler:

Terminal
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.

Then we compile our program using the Rust compiler:

Terminal
rustc src/main.rs -l multiply.o -L .

The -l multiply.o 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:

Terminal
$ ./main
Result: 25000

4. Automate 🤖 #

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:

Cargo.toml · TOML
10
11
[build-dependencies]
cc = "1.0"

Create a build.rs and add compile instructions:

build.rs · Rust
1
2
3
4
5
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:

Terminal
cargo run

See the final result above.

Notes #

Further reading #