Using a C Library From Rust
06 Nov 2018Introduction
Rust is a relatively new systems programming language designed and developed at Mozilla. It is touted as a competitor to C and C++, but is designed in a way that allows you to write high-level-esque code with zero overhead.
At Cisco, I work on writing low-level code for IOS-XR, one of Cisco’s most popular router operating systems. The vast majority of the code we write is in C, so I decided to gauge how easy (or difficult!) it is to use leverage existing C libraries developed in-house in new Rust code. I have also been interested in learning Rust for quite some while, so it’s a win-win!
chip: A Sample C Library
Suppose we want to use a library called chip in our Rust code. The library exports the following two functions:
/**
* Returns a list of temperatures for the given chip instance.
*/
int chip_get_temps(uint32_t chip_inst, float *ret_temps);
/**
* Returns the state of the given chip instance.
*/
int chip_get_state(uint32_t chip_inst, chip_state_t *state);
Note the custom type in the second function. It is defined as follows:
typedef enum {
POWERED_OFF, POWERED_ON
} chip_state_t;
Based on the above, the chip library is probably talking to some kind of hardware device, but we don’t need to worry about that.
Setting Up a Project
After installing Rust, you can use Cargo to create a new project: cargo new <my-project>. This will create a new folder in the current directory, initialize a Git repo, and create a Cargo.toml file.
If you look under <my-project>/src, you will see a barebones main.rs with a main() function. This is the entry point for your program.
Let’s create a deps/ directory and copy our library – libchip.so – into that directory.
To tell the Rust compiler (rustc) that we are linking against a C library, we need to create a build script. Put the following code into build.rs in the root project directory:
fn main() {
let deps_dir = "./deps";
println!("cargo:rustc-link-search=native={}", deps_dir);
println!("cargo:rustc-link-lib=dylib=chip");
}
Our build script is simple: it prints out two “commands” that will be parsed by rustc:
- “Add
deps/to your library search path” - “Link against a library called
chip”
With this, we should be ready to write some Rust.
Wrapping in Rust
Before we can use anything from chip, we need to define the relevant functions in our Rust code.
Let’s start with the enum. Add the following to the top of main.rs:
#[repr(C)]
enum ChipState {
PowerOff, PowerOn
}
We are using a standard Rust enum, but with a repr(C) attribute. Basically, this tells the compiler to set the enum’s layout to that of C.
Next, let’s define the function signatures in Rust:
extern "C" {
fn chip_get_temps(chip_inst: u32, ret_temps: *mut f32);
fn chip_get_state(chip_inst: u32, state: *mut ChipState);
}
The mapping from C to Rust is straightforward. The signatures are wrapped in extern "C" to indicate that their implementations will be linked in from a C library. We use raw pointers (*) because this is what C expects. The pointers are marked as mut * because they will be mutated within the C functions.
Calling C from Rust
Using the above, we can easily call both of these functions:
// Number of temperatures reported per instance
// This would be a #define in the chip header file
const NUM_TEMPS: isize = 10;
fn main() {
// Random chip instance
let chip_inst: u32 = 3;
// Create a Vec of floats of the required size
// Note: This is where the C call will write its results
let mut temps_buf: Vec<f32> = vec![0.0; NUM_TEMPS];
// Get handle to the Vec's underlying raw ptr
let temps_buf_ptr = temps_buf.as_mut_ptr();
// All C calls must be wrapped in an unsafe block!
unsafe {
let err = chip_get_temps(chip_inst, temps_buf_ptr);
if err != 0 {
panic!("Error getting temps!");
}
}
println!("Temperatures = {:?}", temps_buf);
// Now let's query the chip state
let mut state: ChipState = PowerOff; // Default
unsafe {
// We pass in a mutable pointer to the state variable
let err = chip_get_state(chip_inst, &mut state);
if err != 0 {
panic!("Error getting state!");
}
}
match state {
ChipState::PowerOff => println!("Chip is powered off..."),
ChipState::PowerOn => println!("Chip is powered on!")
}
}


Discussion