Reputation: 2020
I am porting a static library to Rust that will be linked with a C application which will provide a global array. Here is the definition of the struct and array:
typedef struct glkunix_argumentlist_struct {
char *name;
int argtype;
char *desc;
} glkunix_argumentlist_t;
extern glkunix_argumentlist_t glkunix_arguments[];
There is no length parameter, instead the last entry in the array will be {NULL, 0, NULL}
(example), which you test for when looping through the array.
I think this is the correct Rust representation for the struct:
#[repr(C)]
struct GlkUnixArgument {
name: *const c_char,
argtype: c_int,
desc: *const c_char,
}
The Rust Nomicon shows how to define an extern for a single integer.
I've seen that if you have a length parameter you can use std::slice::from_raw_parts
to get a slice, but we don't have a length yet. I could modify the C code to provide one, but I would like to be able to provide a drop-in replacement if I can.
I haven't yet seen how to just create one single Rust struct from a extern. Though I did just have the idea of just std::slice::from_raw_parts
with a length of 1.
Is there a better way to more directly define an extern struct in Rust?
Upvotes: 2
Views: 780
Reputation: 42859
You must calculate the length of your C array before creating the slice with from_raw_parts
:
use libc::{c_char, c_int};
#[repr(C)]
pub struct GlkUnixArgument {
name: *const c_char,
argtype: c_int,
desc: *const c_char,
}
pub unsafe fn glkunix_arguments() -> &'static [GlkUnixArgument] {
extern "C" {
pub static glkunix_arguments: *const GlkUnixArgument;
}
let len = (0..)
.take_while(|i| {
let arg = glkunix_arguments.offset(*i);
(*arg).name != std::ptr::null()
|| (*arg).argtype != 0
|| (*arg).desc != std::ptr::null()
})
.count();
std::slice::from_raw_parts(glkunix_arguments, len)
}
You can even map the C struct to a Rust struct:
use libc::{c_char, c_int, strlen};
use std::{ptr, slice, str};
pub struct GlkUnixArgument {
pub name: &'static str,
pub argtype: i32,
pub desc: &'static str,
}
pub unsafe fn glkunix_arguments() -> Vec<GlkUnixArgument> {
extern "C" {
pub static glkunix_arguments: *const GlkUnixArgument_;
}
#[repr(C)]
pub struct GlkUnixArgument_ {
name: *const c_char,
argtype: c_int,
desc: *const c_char,
}
impl GlkUnixArgument_ {
fn is_not_empty(&self) -> bool {
self.name != ptr::null() || self.argtype != 0 || self.desc != ptr::null()
}
}
unsafe fn c_str_to_rust_str(s: *const i8) -> &'static str {
str::from_utf8(slice::from_raw_parts(s as *const u8, strlen(s))).unwrap()
}
let len = (0..)
.map(|i| glkunix_arguments.offset(i))
.take_while(|&arg| (*arg).is_not_empty())
.count();
slice::from_raw_parts(glkunix_arguments, len)
.iter()
.map(|args| GlkUnixArgument {
name: c_str_to_rust_str(args.name),
desc: c_str_to_rust_str(args.desc),
argtype: args.argtype,
})
.collect()
}
Using a lazy construct can ensure that you're doing the operation only once.
Upvotes: 1