Calling C code from Rust
Tags: rust, ffi, c, tutorial
Published: 2022-08-17 20:03:53

Rust was originally designed as a systems language for writing high performance applications that are usually written in C or C++. Although Rust solves problems that C/C++ developers have been struggling with for a long time there are countless amount of C and C++ libraries and it makes no sense to replace all of them with Rust. For this reason, Rust makes it easy to communicate with C APIs without overhead, and to leverage its ownership system to provide much stronger safety guarantees for those APIs at the same time.

Rust provides a foreign function interface (FFI) to communicate with other languages. Following Rust's design principles, the FFI provides a zero-cost abstraction where function calls between C and Rust have identical performance to C function calls. FFI bindings can also leverage language features such as ownership and borrowing to provide a safe interface that enforces protocols around pointers and other resources. 

In this post we'll explore how to call C code from Rust and to build C and Rust code together as well as how to automate this process. You can find the source code of the complete project on [github](https://github.com/samkevich/rust_c_ffi).

> **Note:** As C++ has no stable ABI for the Rust compiler to target, it is recommended to  use C ABI for any interoperability between Rust and C++. Therefore, in this article we will talk about interacting with C code, because the concepts provided here cover both C and C++.

## Creating bindings

We will start with a simple example of calling C code from Rust. Let's imagine that we have the following header file:
```C
/* File: libvec/vec.h */
#pragma once

typedef struct Vec2 {
    int x;
    int y;
} Vec2;

void scale(Vec2* vec, int factor);

```
and corresponding C file:
```C
/* File: libvec/vec.c */
#include "vec.h"

void scale(Vec2* vec, int factor) {
    vec->x *= factor;
    vec->y *= factor;
}

```
Before we start using any C code from Rust, we need to define  all data types and function signatures we are going to interact with in Rust. Usually in C/C++ world we use header files for that purpose, which define all necessary data. But in Rust, we need to either manually translate these definitions to Rust code, or use a tool to generate these definitions.

Let us write a rust file which resembles the C header file:
```Rust
/* File: src/bindings.rs */
use std::os::raw::c_int;

#[repr(C)]
#[derive(Debug)]
pub struct Vec2 {
    pub x: c_int,
    pub y: c_int,
}

extern "C" {
    pub fn scale(
        vec: *mut Vec2,
        factor: c_int
    );
}
```

Rust needs to laydown members in a structure the way C would laydown it in memory. For that reason we include the `#[repr(C)]` attribute, which instructs the Rust compiler to always use the same rules C does for organizing data within a struct.

We declared function `scale` without defining a body. So, we use `extern` keyword to indicate the fact that the definition of this function will be provided elsewhere or linked into the final library or binary from a static library.

In the `extern` block, we have a string, `"C"`, following it. This `"C"` specifies that we want the compiler to respect the C ABI so that the function-calling convention follows exactly as a function call that's done from C. An Application Binary Interface(ABI) is basically a set of rules and conventions that dictates how types and functions are represented and manipulated at the lower levels. There are also other ABIs that Rust supports such as `fastcall`, `cdecl`, `win64`, and others, but this topic is beyond the scope of the current article.

## Generating bindings

So far we manually wrote interface to interact with C. But this approach has some drawbacks. Consider real case scenario where each header file has lots of structure definitions, function declarations, inline functions etc. As well as writing interfaces manually is tedious and error prone. But luckily for us there is a tool called [bindgen](https://github.com/rust-lang/rust-bindgen) which can perform these conversions automatically.

We can install `bindgen` with the following command:
```Bash
$ cargo install bindgen
```
The command to generate bindings may look like this:
```Bash
$ bindgen libvec/vec.h -o src/bindings.rs
```

Although we can use `bindgen` as a command line tool because it seems to give you more control over generation,  the recommended way to use `bindgen` is generating the bindings on the fly through `build.rs` script.

A `build.rs` script is a file written in Rust syntax, that is executed on your compilation machine, after dependencies of your project have been built, but before your project is built. If you want to know more about `build.rs` please refer to the [documentation](https://doc.rust-lang.org/cargo/reference/build-scripts.html). To generate bindings with `bindgen` we add the `build.rs` file with the following content:
```Rust
/* File: build.rs */
extern crate bindgen;
use std::path::PathBuf;

fn main() {
    // Tell cargo to invalidate the built crate whenever the wrapper changes
    println!("cargo:rerun-if-changed=libvec/vec.h");
    let bindings = bindgen::Builder::default()
        // The input header we would like to generate bindings for.
        .header("libvec/vec.h")
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        // Finish the builder and generate the bindings.
        .generate()
        .expect("Unable to generate bindings");

    // Write the bindings to the src/bindings.rs file.
    let out_path = PathBuf::from("src");
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

```
We also need to add `bindgen` to build dependencies in `Cargo.toml` file:
```toml
[build-dependencies]
bindgen = "0.53.1"
```
Now, when we run `cargo build`, our bindings are generated on the fly to `src/bindings.rs` file.

Finally, we can use our generated buildings in Rust as in the following example:
```Rust
/* File: src/main.rs */
use crate::bindings::{scale, Vec2};

mod bindings;

fn main() {
    unsafe{
        let mut vec = Box::new(Vec2{ x: 5, y: 10 });
        scale(&mut *vec, 2);
        println!("scaled vector: {:?}", *vec);
    }
}

```
## Building C code

Since the Rust compiler does not directly know how to compile C code, we need to compile our non-Rust code ahead of time. We can start simple and compile it in static library, which then will be combined with our Rust code at the final linking step.

To compile static library `libvec.a` from our C code we can use the following command:
```Bash
$ cc -c libvec/vec.c
$ ar rcs libvec.a vec.o
```
Now we can link it all together:
``` Bash
$ rustc -l static=vec -L. src/main.rs
```
This command tells Rust compiler to look for a static `libvec.a` in the current `.` path. This should compile and give you the main binary, which can be executed.

## Using `cc` crate

It might be quite tedious to compile static library manually every time we make changes in C code. The better solution is to instead utilize the [cc](https://github.com/rust-lang/cc-rs) crate, which provides an idiomatic Rust interface to the compiler provided by the host.

In our case of compiling a single C file as a dependency to a static library, we can amend out `build.rs` script using the `cc` crate. The modified version would look like this:
```Rust
/* File: build.rs */
extern crate bindgen;
extern crate cc;
use std::path::PathBuf;

fn main() {
    // Tell cargo to invalidate the built crate whenever the wrapper changes
    println!("cargo:rerun-if-changed=libvec/vec.h");
    let bindings = bindgen::Builder::default()
        // The input header we would like to generate bindings for.
        .header("libvec/vec.h")
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        // Finish the builder and generate the bindings.
        .generate()
        .expect("Unable to generate bindings");

    // Write the bindings to the src/bindings.rs file.
    let out_path = PathBuf::from("src");
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");

    // Build static library
    cc::Build::new()
        .file("libvec/vec.c")
        .compile("libvec.a");
}

```
Updated build dependencies section in `Cargo.toml` would look like this:
```toml
[build-dependencies]
bindgen = "0.53.1"
cc = "1.0"
```
Now we can simply run `cargo build` to generate bindings, compile static library and link it with a Rust code. How cool is that!

## Using CMake

In real world we usually have existing C projects that often use popular build systems like `make` or `CMake `. And it would be very inconvenient to use `cc` crate to compile them and don't have an option to reuse existing makefiles. Likely for us Rust have [cmake](https://crates.io/crates/cmake) crate that is intended to help us.

Let's assume that our `vec` library uses `CMake` and we have the following `CMakeLists.txt` file:

```C
# File: libvec/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(libvec C)

add_library(vec STATIC vec.c)

install(TARGETS vec DESTINATION .)
```

To use `cmake` crate we need to add build dependency on it to `Cargo.toml` file:
```toml
[build-dependencies]
bindgen = "0.53.1"
cmake = "0.1.48"
```
And modify our `build.rs` file as follows:
```Rust
/* File: build.rs */
extern crate bindgen;
extern crate cmake;
use cmake::Config;
use std::path::PathBuf;

fn main() {
    // Tell cargo to invalidate the built crate whenever the wrapper changes
    println!("cargo:rerun-if-changed=libvec/vec.h");
    let bindings = bindgen::Builder::default()
        // The input header we would like to generate bindings for.
        .header("libvec/vec.h")
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        // Finish the builder and generate the bindings.
        .generate()
        .expect("Unable to generate bindings");

    // Write the bindings to the src/bindings.rs file.
    let out_path = PathBuf::from("src");
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");

    // Build static library
    let dst = Config::new("libvec").build();
    println!("cargo:rustc-link-search=native={}", dst.display());
    println!("cargo:rustc-link-lib=static=vec");
}

```
We use `cmake::Config` type to trigger `CMake` driven to build  our library, telling that it’s code and `CMakeFiles.txt` files are located in `libvec` subdirectory and then requesting the build to happen. Next lines write to stdout special command for Cargo to set library search path and pick our `libvec.a` for linking respectively. After that we can compile our project with `cargo build`.

## Summary
We've talked today about Rust and C interoperability, how to generate bindings to C code and how to automate this process as well as how to use `cc` and `CMake` crates to build C code from cargo.

I hope you have found the information above useful and helpful, any questions and feedbacks and welcomed.
If you find this article interesting don't forget to like it and give a start to [source code](https://github.com/samkevich/rust_c_ffi).