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.