scribble

Tibi's blog

About LinkedIn GitHub

10 Mar 2015
There and back again - moving between Rust and C

For some time past I’m working with Rust in my free time. I think we can see a tendency among programming languages. The native languages are gaining more momentum nowadays. Maybe it was started by Go, I don’t know, but you can certainly feel it.

One of the newest languages out there is Rust, which isn’t just a ‘Yet Another Programming Language’. It really provides new features compared to other languages, for example the ownership-borrowing model and it’s easy integration into other 'C compatible’ languages. You get some higher level stuff with full control over lower level stuff and you really need to strive to shot yourself in the foot.

In this post I’ll talk about how to create interfaces between Rust and C libraries. I’ve created a Rust binding for syslog-ng (a rock solid message broker) so people can write custom filter in Rust from now on. It’s under active development and I intend to extend it with other features as well (sources, destinations, etc.).

Using Rust stuff from C

Functions

Rule 1: use #[no_mangle] and pub extern fn when you declare your functions:

#[no_mangle]
pub extern fn rust_filter_proxy_init(this: &mut RustFilterWrapper) {
           this.filter.init()
}

This will export your function with the name you gave it. You can examine the #[no_mangle]’s effect with the nm program.

Rule 2: You can return Box<> as *mut

You may write a function which creates an object in Rust and returns it to the C caller. Their memory represenation is the same, so it’s OK to do it.

Rule 3: You can borrow pointers from C

I would recall to the first example. Do you see the function’s argument? It was a Box<> passed to and got back as a reference.

Rule 4: You can’t pass trait objects into C. Wrap them into a struct.

I wanted to hand over a newly created trait object to C, but it didn’t work. Wrap it into a struct on the heap and you can pass it freely to C to get it back.

pub struct RustFilterWrapper<'a> {                                                                                                               
        pub filter: Box<RustFilter<'a>>
}

Rule 5: Converting a char* string into &str is cheap. Very cheap.

Rust doesn’t use the trailing 0 byte, and it’s &str is just a pointer with a size. When you convert a char* to &str, Rust counts the bytes before the 0 character, uses it as the size of the Rust string and makes the pointer to point at the gchar*. This leaves the memory deallocation to C.

I use the following fuction for this purpose:

pub fn from_c_str_to_owned_str<'a>(string: * const gchar) -> &'a str {
    unsafe {
      return str::from_utf8(cstr::from_ptr(string).to_bytes()).unwrap();
    };
}

Rule 6: write header files for your exported functions

You will certainly link your Rust library to a C library, which will need to know about the exported Rust functions in compile time. You have to write a header file which contains these definitions and include it wherever you use your exported functions.

Rule 7: create nice *_free() functions

The situation is the same: you created an object in Rust, worked with it through FFI and you want to free that object. The best approach is to use a simple function which takes over the ownership of your object:

#[no_mangle]
pub extern fn rust_filter_proxy_free(_ : Box<RustFilterWrapper>) {
}

+1 trick: you may have to use some C functions which operate on a struct. You want to write a higher level Rust binding for them but they receive the object as mutable so you can’t use simply &self. You know, that it’s a very nice function which doesn’t touch it’s argument. You can work around this problem by declaring the C function in Rust with *const arguments. You must check that the function really doesn’t modify the argument.

Structs and enums

You can use the #[repr(C)] on struct and #[repr(<type comes here>)] on enums. Be aware, that an enum’s size is compiler specific. If you have 5 values in it, the compiler may choose to use a byte for their representation or an integer.

#[repr(u32)]
pub enum ActType {
    UNDEFINED = ffi::AT_UNDEFINED,
    PROCESSED = ffi::AT_PROCESSED,
    ABORTED = ffi::AT_ABORTED
}

If you need to use in Rust a struct from C as a pointer (so you are not interested in it’s fields, just pass it over) you can declare it as an empty struct.

struct astructfromc;

A binding generator can make your life happy

The situation is you want to use some functions from C. You have to define them in Rust and a binding generator can help you in this process. I think this tool is very great:

https://github.com/crabtw/rust-bindgen


Till next time,
Tibor Benke at 00:00

scribble

About LinkedIn GitHub