DEV Community

Dave
Dave

Posted on

How to call C code from Rust

Calling C code from Rust falls under FFI (Foreign Function Interface) and is unsafe by nature, as it's crossing Rust boundary and all the checks that comes with it.

The syntax itself is pretty simple, so create a project and:

// src/main.rs
extern "C" {
    fn print_num(num: i32);
}

fn main() {
    println!("[rust] start");
    unsafe { print_num(50) }
}
Enter fullscreen mode Exit fullscreen mode

We are defining an external function which uses C ABI convention - which has got to do with the way stack and registers are arranged, when making a call to another function.

And for our C library:

// src/num.c
#include <stdio.h>

void print_num(int num) {
    printf("[  c ] num is %d", num);
}
Enter fullscreen mode Exit fullscreen mode

But if we cargo run here, we'd get main.rs:8: undefined reference to print_num, meaning compiler is not finding our C function so we need to link the library:

$ # in root of project 
$ cc -c src/num.c
$ cc -shared num.o -o libnum.so
Enter fullscreen mode Exit fullscreen mode

First cc would generate lib.o but rustc needs a shared obejct, which is generated from that and starts its name with lib. Now we can link it to our Rust code (omit the lib at start):

$ rustc -l num -L . src/main.rs
Enter fullscreen mode Exit fullscreen mode

Which looks for (lib)num.so in current . path. This should compile and give you the main binary, which can't be executed:

$ ./main
./main: error while loading shared libraries: libnum.so: cannot open shared object file: No such file or directory

$ ldd main
    …
    libnum.so => not found
Enter fullscreen mode Exit fullscreen mode

Because you've used a dynamic library, and ldd can't find it, you can't run your binary. You can either install libnum.so in appropriate path or use a static library:

$ ar rcs libnum.a num.o
$ rustc -l static=num -L. src/main.rs  
$ ./main
[rust] start
[  c ] num is 50
Enter fullscreen mode Exit fullscreen mode

Perfect! Let's simplify our workflow, create build.rs in root of project:

fn main() {
    println!("cargo:rustc-link-search=.");
    println!("cargo:rustc-link-lib=static=num");
}
Enter fullscreen mode Exit fullscreen mode

And now we can use cargo run and cargo build the usual way. We can also declare the name of library where it's being used so instead of println!("cargo:rustc-link-lib=static=num"); in build.rs:

// src/main.rs
#[link(name = "num", kind = "static")]
extern "C" {
    fn print_num(num: i32);
}
Enter fullscreen mode Exit fullscreen mode

which links libnum.a and if we remove the kind part defaults to dynamic versin libnum.so.

Finally if you're not interested in handling the compilation of library files yourself, there's cc crate which takes care of that for you:

// Cargo.toml
…
[build-dependencies]
cc = "1.0"
Enter fullscreen mode Exit fullscreen mode

And your build.rs:

// build.rs
fn main() {
    cc::Build::new()
        .file("src/num.c")
        .compile("anything");
}
Enter fullscreen mode Exit fullscreen mode

And that's all!

Top comments (0)