0

I've written a function in C with this signature:

write_stuff(char **pages, uint32 page_count);

pages will be an array of 128 elements, with each element either NULL or pointing to a char array of 8192 bytes. So, an array of arrays with fixed, known sizes.

I need to populate the arrays in Rust and pass them to the C function. When I generate a Rust signature using bindgen, it produces this:

extern "C" {
    pub fn write_stuff(pages: *mut *mut ::std::os::raw::c_char, page_count: u32,);
}

Now, how do I declare a variable in Rust that I can populate and pass to this function?

I tried this:

let mut pages = [Option <[u8; 8192]>; 128];

but it doesn't compile.

This does compile:

type MyPage = Option<[u8; 8192]>;
let myarray: [MyPage; 128] = [None; 128];

but I'm not clear on whether it would store the data in the right format, or how to cast it appropriately:

let pages: *mut *mut ::std::os::raw::c_char = myarray.as_mut_ptr() as // what goes here?;
ccleve
  • 15,239
  • 27
  • 91
  • 157

1 Answers1

2

I believe the Rust type you're looking for is &[Option<&[u8; 8192]>]. The C version uses a double pointer, so you need to make sure the inner type is a reference . Option<&T> shows that the reference is nullable and the null pointer optimization guarantees that it has the same size as T.

I've tested that the code below works as expected, but I'm not 100% sure this type conversion is sound so I'd be careful using this in production code.

Rust main.rs

#[link(name = "mylib")]
extern "C" {
    pub fn write_stuff(pages: *mut *mut ::std::os::raw::c_char, page_count: u32);
}

pub fn write_stuff_rust(pages: &[Option<&[u8; 8192]>]) {
    unsafe { write_stuff(pages.as_ptr() as *mut _, pages.len() as u32) }
}

fn main() {
    let page1: [u8; 8192] = [b'a'; 8192];
    let page3: [u8; 8192] = [b'b'; 8192];
    let page4: [u8; 8192] = [b'c'; 8192];
    let page7: [u8; 8192] = [b'd'; 8192];

    let pages: Vec<Option<&[u8; 8192]>> = vec![
        Some(&page1),
        None,
        Some(&page3),
        Some(&page4),
        None,
        None,
        Some(&page7),
    ];

    write_stuff_rust(&pages);
}

C mylib.c

#include <stdio.h>
#include <stdint.h>

void write_stuff(char **pages, uint32_t page_count) {
    for (uint32_t i = 0; i < page_count; i++) {
        printf("%d=%p\n", i, pages[i]);
        if (pages[i]) {
            for (size_t j = 0; j < 8192; j++) {
                putchar(pages[i][j]);
            }
            putchar('\n');
        } else {
            puts("<empty>");
        }
    }
}
apilat
  • 1,370
  • 8
  • 17
  • Thank you, this is outstanding. You've gone above and beyond. – ccleve Mar 19 '22 at 19:01
  • This is sound, however: a) better to use `&[Option<&[u8; 8192]>; 128]` (gives the compiler more knowledge, thus better optimizations). b) If the C code changes the references it's UB (since they're created from shared references), and if it's not it's better to change them to `*const` (or `const *` in C). – Chayim Friedman Mar 20 '22 at 21:42
  • Caution. The code above works, but similar code I wrote did not because I created the pages vec in a function and returned a raw pointer to it. The vec went out of scope and its memory was freed. Rust does not track the lifetime of raw pointers. – ccleve Apr 24 '22 at 22:32