Search code examples
rustsliceffi

How does one convert a C pointer-to-pointer to a Rust slice of slices?


Say I have a C float**, which I index in C like float[x][y]. Is it possible to pass that float** to Rust and convert it to an &mut [&mut [f32]] so I can handle it more "rustily" within Rust?

It wasn't too complicated to convert an one-dimensional array pointer using std::slice::from_raw_parts_mut, but I'm completely stumped on how to do the same with a two-dimensional array pointer without angering the borrow checker or incurring unnecessary allocations...


Solution

  • The most you can possibly do by just converting the pointer itself is &mut [*mut f32]. This is because of two facts about the Rust types involved:

    • Rust slice pointers contain lengths. That is, an &mut [f32] has more information than just the address of the f32 data — more information than C stores.
    • Rust slices are contiguous sequences of elements (like C arrays). If you have an &mut [T] then there must be some location in memory where Ts are stored adjacent to each other.

    What you get from C is described in Rust syntax as *mut *mut f32. This pointer points to a contiguous sequence of just addresses (pointers) — no lengths. Therefore, you do not have a [*mut [f32]], which would have both addresses and lengths; only an [*mut f32], with addresses only. Since you don't have an [*mut [f32]] anywhere, you definitely can't construct an &mut [*mut [f32]].

    You have two choices:

    1. Perform the allocation you didn't want to do — build a Vec<&mut [f32]> with the appropriate lengths for each inner slice, and return that.
    2. Instead of trying to produce an [&mut [f32]] of any sort, write a custom wrapper type around the pointer, with a method or std::ops::IndexMut implementation that produces &mut [f32]s on demand. Or, write an iterator which returns &mut [f32], if you only need sequential rather than random access.

    The good news is, option 2 is perfectly good idiomatic Rust! Even in pure Rust code with no FFI involved, you shouldn't use &mut [&mut [f32]], because it almost always demands a temporary allocation to hold the inner &muts.