C-Bindings with Rust

Enable Javascript to display Table of Contents.
The sources of the example project are available at github.com.

Preparation

Install clang with apt install llvm-dev libclang-dev clang (clang version >= 3.9 is recommended)

Project Setup

Create a new directory and place a Cargo.toml with the following content (see doc.rust-lang.org for more details):
[workspace]
members = [
	"example",
	"library"
]
Then create an application sub-project called example and a library sub-project called library:
$ cargo new example
     Created binary (application) `example` package
$ cargo new library --lib
     Created library `library` package
$
Build the project to verify that it's setup correctly:
$ cargo build
   Compiling library v0.1.0 (/home/charly/Projects/RUST/rust-cbinding-template/library)
   Compiling example v0.1.0 (/home/charly/Projects/RUST/rust-cbinding-template/example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
$

Library with C-Binding

In a first step, add a bindgen dependency to Cargo.toml. As version, use the latest version from crates.io:
[build-dependencies]
bindgen = "0.59"
In a next step, we create a C header file, which includes all headers we want to provide to RAST. In this file (bindings.h) we also may add replacement types and blocklisting:
#ifndef BINDINGS_H
#define BINDINGS_H

#include "libfoo/foo.h"

#endif // BINDINGS_H
In the next step, we create a build.rs. It's a RUST program which output is parsed to handle e.g. the compilation and FFI generation of our C library. For compiling the library and creating a bingen tutorial
extern crate bindgen;

use std::env;
use std::path::PathBuf;
use std::process::Command;

fn main() {
    // Compile libfoo with make
    Command::new("make")
        .arg("-C")
        .arg("libfoo")
        .arg("CROSS_COMPILE=")
        .output()
        .expect("failed to execute process");

    // Add dependency to libfoo
    println!("cargo:rustc-link-lib=foo");

    println!("cargo:rerun-if-changed=bindings.h");

    let bindings = bindgen::Builder::default()
        .header("bindings.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}
The header file of our C code looks like this:
#ifndef LIBFOO_FOO_H
#define LIBFOO_FOO_H

void foo_reset();
int foo_get_value();
void foo_set_value(int value);

#endif // LIBFOO_FOO_H
When running cargo build a file called bindings.rs is generated in the target directory:
/* automatically generated by rust-bindgen 0.59.1 */

extern "C" {
    pub fn foo_reset();
}
extern "C" {
    pub fn foo_get_value() -> ::std::os::raw::c_int;
}
extern "C" {
    pub fn foo_set_value(value: ::std::os::raw::c_int);
}
This generated file can be included in your lib.rs and thus will be the public API for your crate:
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
Source: RUST book and bindgen user's manual

Example Application

To be able to use our library crate, we have to add it as dependency in the example/Cargo.toml:
[dependencies]
raw_foo = { path = "../library" }
In the application itself, we can either use use raw_foo::*; to import all methods at once, or call them with the crate prefix:
fn main() {
    unsafe {
        println!("initial raw value: {}", raw_foo::foo_get_value());
        raw_foo::foo_set_value(13);
        println!("raw value after setting 13: {}", raw_foo::foo_get_value());
        raw_foo::foo_reset();
        println!("raw value after reset: {}", raw_foo::foo_get_value());
    }
}