How to represent pointer to dynamic array rust FFI

Hi,

I have a C library which I want to replace with Rust, but it already has other C consumers so I cannot change the layout of the main struct owner. I'm struggling to figure out the best way to represent this in Rust. In the below C, struct owner owns the containers dynamic array and is represented as a pointer to that array.

typedef struct
{

    int n;
    container* containers
} owner;

typedef struct
{
    int a;
} container;

Is this the most appropriate repr(C) equivalent?

pub struct owner {
    n: c_int,
    containers: Box<[container]>,
}

pub struct container {
    a: c_int,
}

You can't use Box<[Container]> in the place of a Container * because boxed slices are fat pointers (2x usize wide) while raw pointers are only 1x usize wide. That means your Rust owner won't have the same layout.

If it were me, I'd create an exact copy of the C type, complete with the raw pointers and int length field. Then in Rust, implement Drop to take care of memory management and implement Deref so people can still treat it as a slice.

#[repr(C)]
struct Owner {
  n: c_int,
  ptr: *mut Container,
}

#[repr(C)]
struct Container { 
  a: c_int,
}

impl Deref for Owner {
  type Target = [Container];
  fn deref(&self) -> &[Container] {
    unsafe { std::slice::from_raw_parts(self.ptr, self.n as size ) }
  }
}

In general, I try to avoid conflating C types with Rust types. Yes, technically Box is equivalent to a pointer, but you also need to worry about things like which allocator was used to allocate the memory and all that.

It's easier to enforce those things by writing the code out manually and sprinkle // Safety: ... comments through your code than rely on the implicit transmutes you get when going back and forth across the FFI boundary.

3 Likes

Ah this makes a lot of sense now!

Can you clarify these two scenarios as well?

  1. Struct owns another struct indirected with a pointer. E.g.
typedef struct
{
    jobdetails* job
} person;

typedef struct
{
    int a;
    int b;
    // ..
} jobdetails;

Would this be?

struct person {
    job: Option<Box<jobdetails>>,   // Option as pointer could be technically null
}

struct jobdetails {
 // ...
}
  1. Struct has a reference to another struct, but doesn't own it. (Using same C example)
struct person<'a> {
   job: Option<&'a mut jobdetails>, 
}

struct jobdetails {
    // ...
}

If you allocate from the rust side, this is how this could look like

use std::{ffi::c_int, ptr};

#[repr(C)]
pub struct owner {
    n: c_int,
    containers: *mut container,
}

#[derive(Copy, Clone)]
#[repr(C)]
pub struct container {
    a: c_int,
}

impl owner {
    fn new(value: c_int, len: usize) -> Self {
        assert!(len < c_int::MAX as usize);
        let data = vec![container { a: value }; len];

        Self {
            n: len.try_into().unwrap(),
            containers: Box::into_raw(data.into_boxed_slice()).cast(),
        }
    }
}

impl Drop for owner {
    fn drop(&mut self) {
        let Self { n, containers } = self;
        let len: usize = (*n).try_into().unwrap();
        let ptr = ptr::slice_from_raw_parts_mut(*containers, len);
        let a = unsafe { Box::from_raw(ptr) };
        std::mem::drop(a);
    }
}

At least miri seams to be happy with it.