Idiomatic way to pass nested slices from C to rust

Hello Rust community,

I am rustifying a bit of my code base and I am trying to integrate a rust module into a larger C code base.

The C code base has a lot of dynamic lists, implemented as a struct with a length and a buffer:

#[repr(C)]
struct CSeq<T> {
    len: usize,
    buffer: *const T
}

My aim is to keep the Rust code base clean, so I do not want C types leaking into the Rust code. Therefore, I thought it would be a good approach to have thin wrappers which convert the C types into idiomatic Rust types and call functions with a Rust signature. I want to avoid copies and new allocations in these wrappers, but if I have to make them, so be it.

Since in my use case, the data is passed read-only to Rust, I thought the best implementation would be a slice, so I implemented Deref.

impl<T> std::ops::Deref for CSeq<T> {
    type Target = [T];

    fn deref(&self) -> &Self::Target {
        unsafe { std::slice::from_raw_parts(self.buffer, self.len) }
    }
}

This feels very Rusty, as the wrappers are indeed thin and I can directly call the actual Rust functions which accept slices as input.

Now however, I have the situation that CSeq can be nested, such as CSeq<CSeq<CSeq<u32>>>. Let's for now look at this specific case.

First, I am wondering what the most idiomatic call signature from the Rust side would look like. I assume &[&[&[u32]]] is ugly but most flexible (even though using that if you have a Vec<Vec<Vec<u32>>> seems like a lot of pain as well).

Second, how do I ideally convert the nested CSeq into that format? Is there some magic one can use here, or is repeatedly creating Vecs of slices and then slices from these Vecs the way to go? In the end, I also want to avoid writing a lot of boiler plate code by hand for different nesting levels; is there some clever way to leverage the compiler here (like with Generics and Deref in the unnested case?) or is there at least a solution which I could readily put into a macro?

I would be very grateful for pointers into the right direction. :slight_smile:

You would have to, because you don't have a slice of contiguous &[u32] or &[&[u32]] in memory (and the layout of (wide) slice pointers is not guaranteed).

The first point applies to nested Vec as well; you can't get a &[&[&[u32]]] from a &Vec<Vec<Vec<u32>> without allocating new Vecs to hold the inner slices or similar.


So, what does one do instead in the Vec situation? One probably just uses &[Vec<Vec<u32>>] instead. In your case that would mean &[CSeq<CSeq<u32>>]. If you don't want to expose CSeq (which seems to be the only pertinent difference), you could use a dedicated newtype around it instead.

Generics are another option for avoiding the specific type on the receiving side, but it can get awkward...

fn example<T, U>(_: &[T])
where
    T: Deref<Target = [U]>,
    U: Deref<Target = [u32]>,
{
}

...and I don't really see a benefit over exposing some type that's just dedicated to being an otherwise opaque implementor of Deref (unless the function already needed to be that flexible anyway).

If there is a fixed set of concrete types, you could newtype each, but that's going directly against your wish to avoid boilerplate.