FFI and nested structs

Hi guys,

I have a set of nested structs that I am trying to expose to C via Rust's FFI. The code is simple enough but the pointers are going berserk (<= technical term) and I'm getting all sorts of spurious results.

Here's a trivial example:

lib.rs

#[repr(C)]
pub struct Car {
    name: *const libc::c_char,
    engine: Engine,
}

#[repr(C)]
pub struct Engine {
    size: *const libc::c_char,
    make: *const libc::c_char
}

#[no_mangle]
pub extern "C" fn get_car() -> Car {    
    Car {
        name: std::ffi::CString::new("Morris").unwrap().as_ptr(),
        engine: Engine {
            size: std::ffi::CString::new("Tiny".to_string()).unwrap().as_ptr(),
            make: std::ffi::CString::new("Terrible").unwrap().as_ptr(),
        },
    }
}

test.h

typedef struct _Engine {
	const char *size;
	const char *make;
} Engine;

typedef struct _Car {
	const char *name;
	Engine engine;
} Car;

extern Car get_car();

test.c

#include <stdio.h>
#include "test.h"

int main () {
	Car car = get_car();
	printf("Name: %s, Engine size: %s, Engine make: %s\n", car.name, car.engine.size, car.engine.make);
}

Output

Most of the time it's just:

Name: Morris, Engine size: , Engine make:

And occasionally (if I run it in a large loop) I'll get some/all the vars to appear:

Name: Morris, Engine size: Tiny, Engine make: Terrible

With more complex structures the results are even more amusing. Could someone tell me what stupid thing I'm doing this time? :confused:

Cheers,

Pete.

1 Like

CString's as_ptr() does not transfer ownership of the data, so your strings get de-allocated as soon as they go out of scope (pretty much immediately). The struct ends up with a bunch of dangling pointers.
You could try using into_raw() instead. In that case you'll have to take care of freeing the memory yourself though (but not with free()! It must be done using CString::from_raw()).
If your strings are static, you can insert null terminators explicitly and directly convert them to pointers, e.g. "Terrible\0".as_ptr()

2 Likes

It's very similar to the caveats with C++'s std::string::c_str().

Rust makes safety promises for references, but most operations with pointers are unsafe.