Calling Rust from C

To call a Rust function in C, we’ll compile the Rust library to a shared object file, and link it to our C code.

First, the Rust code. This is the same as the “Calling Rust from Python” article.

// we want a .so file
#![crate_type = "dylib"]

// no_mangle lets us find the name in the symbol table
// extern makes the function externally visible
#[no_mangle]
pub extern fn square(x: i32) -> i32 {
    x * x
}

Save that as rmath.rs, and compile it:

rustc rmath.rs

The rmath.rs file will be compiled to librmath.so. This is the file we’ll link to. The linking works exactly the same as with any other .so file.

Here’s some example C code:

#include <stdio.h>
#include <stdint.h>

int32_t square(int32_t x);

int main() {
    printf("pow(5, 2) = %d\n", square(5));
    return 0;
}

Save that as example.c, and compile it:

gcc example.c -o example -L. -lrmath

To execute it, we need to tell ldd where to look for the library:

LD_LIBRARY_PATH=. ./example

Structure

Working with structs in Rust should be familiar to C developers, since they work largely in the same way.

For this example, we’ll have a new library we’ll call libstex.so, so save the following to stex.rs and compile it with rustc stex.rs.

#![crate_type = "dylib"]

#[repr(C)]
pub struct Point {
    x: f64,
    y: f64,
}

#[no_mangle]
pub extern fn move_point(p: Point, x_diff: f64, y_diff: f64) -> Point {
    Point { x: p.x+x_diff, y: p.y+y_diff }
}

#[no_mangle]
pub extern fn move_point_inplace(p: &mut Point, x_diff: f64, y_diff: f64) -> () {
    p.x += x_diff;
    p.y += y_diff;
}

As you can tell, in this example we’ve got two similar functions; one returns a new copy of the Point structure and the second modifies the structure in place.

Our C code for this is fairly straight forward:

#include <stdio.h>
#include <stdlib.h>

struct Point {
    double x;
    double y;
};

struct Point move_point(struct Point p, double x_idff, double y_diff);
void move_point_inplace(struct Point *p, double x_idff, double y_diff);

int main() {
    struct Point p;
    p.x = 5.0;
    p.y = 1.0;

    struct Point p2 = move_point(p, 1.0, 3.0);
    printf("Point(%f,%f)\n", p2.x, p2.y);
    move_point_inplace(&p2, -1.0, -3.0);
    printf("Point(%f,%f)\n", p2.x, p2.y);

    return 0;
}

Compile and run it as before:

$ gcc example.c -o example -L. -lstex
$ LD_LIBRARY_PATH=. ./example
Point(6.000000,4.000000)
Point(5.000000,1.000000)

Vectors

Vectors are a bit more work to access. We’ll be writing our own Slice struct containing just a pointer and the length; we could probably access the vector struct directly, but the layout in memory is not guaranteed, so it’s safer to create our own.

As before, here’s the Rust code:

#![crate_type = "dylib"]
use std::mem;

#[repr(C)]
pub struct Slice {
    ptr: *mut i32,
    len: usize,
}

fn vec_return() -> Vec<i32> {
    vec![11, 13, 17, 19, 23, 29]
}

#[no_mangle]
pub extern fn wrapper() -> Slice {
    let mut v = vec_return();
    let p = v.as_mut_ptr();
    let len = v.len();

    unsafe {
        // so that no destructor is run on our vector
        mem::forget(v);
    }

    Slice { ptr: p, len: len }
}

Note: You should change the len to be uint64 instead of usize. I didn’t do it here because usize doesn’t implement to_u64() on my version of Rust.

As with the Structures example, we have to define the Slice struct in C.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

struct Slice {
    int32_t *ptr;
    uint64_t len;
};

struct Slice wrapper(void);

int main() {

    struct Slice s = wrapper();
    for (int i = 0; i < s.len; i++) {
        printf("s[%d] = %d\n", i, s.ptr[i]);
    }

    return 0;
}

Now we can compile and run it:

$ gcc example.c -o example -L. -lstex
$ LD_LIBRARY_PATH=. ./example
s[0] = 11
s[1] = 13
s[2] = 17
s[3] = 19
s[4] = 23
s[5] = 29

And that’s all there is to it! Of course, you’ll probably want to stick the declarations into a .h file, but for simplicity’s sake I haven’t bothered here.

Published on December 23, 2014.