Rust’s os::args() to C’s *argv[]

I’ve been hooked on Rust lately, trying to wrap my head around the whole borrowing, lifetimes and boxes parade. What’s tough with young Rust is that most sample code out there is outdated, answers to questions no longer correct, documentation leaves a lot to be desired.

I decided to learn a bit of Rust by writing a FUSE, using the libfuse shared library (yes, I know there’s a Rust implementation here). So I’m looking at FFI to get me started and writing the main libfuse signature.

fn fuse_main_real(argc: c_int, argv: *const *const c_char, op: *const fuse_operations, op_size: size_t, user_data: *const c_void) -> c_int;

The huge challenge I found myself with was trying to convert os::args to *const *const c_char. All the code out there seems to be outdated, nothing worked for me. I was getting lifetime errors, borrowing errors and even move errors for hours on end. And when there were no borrowing errors I was slapped on the face with segmentation faults.

After a lot of thinking, reading the basics several times, I managed to convert the command line arguments given to my Rust program into the needed pointer to a pointer of chars. Here’s how it looks:

let argv:Vec<ffi::CString> = os::args_as_bytes().into_iter().map(|arg| { ffi::CString::from_vec(arg) } ).collect();
let args:Vec<*const c_char> = argv.into_iter().map(|arg| { arg.as_ptr() } ).collect();
fuse_main_real(args.len() as c_int, args.as_ptr() as *const *const c_char, .... );

Let’s go through this step by step. First we grab a Vec<Vec<u8>> from os::args_as_bytes, iterate and collect a Vec of null-terminated ffi::CStrings. Next, iterate over each CString and collect a pointer to the char array it’s being backed by. The rest is easy.

Why two variables? I found that chaining ffi::CString::from_vec(arg).as_ptr() would not work because once the pointers have been collected the created CString is freed once the map is done. So we need to keep the CStrings around in order to the pointers to be available for use.

A seemingly trivial task turned out to be quite difficult to get to. I’ve probably done it incorrectly either way 😀 it must be, since it’s a highly inefficient solution (looked at the emitted assembly and those two calls are awfully large).

Still a lot to learn about this ever-evolving, unstable but fun language. By the way, I used rustc 1.0.0-dev for the above.