2

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?

6
  • play.rust-lang.org/… Commented Nov 18, 2019 at 3:34
  • @PitaJ But do I need to use anything like from_raw_parts (obviously it would be a different function) before accessing the struct fields? Commented Nov 18, 2019 at 3:53
  • "There is no length parameter" a C array have ALWAYS a length, if not it's not an array by definition. You can have it by doing const size_t len = sizeof glkunix_arguments / sizeof *glkunix_arguments; Commented Nov 18, 2019 at 9:14
  • @Stargateur I meant no length parameter was provided to the functions. I could change the end user code but then it wouldn't be a drop in replacement. Commented Nov 18, 2019 at 11:33
  • 1
    @Stargateur I never said it didn't have a length. I said there's no length parameter, by which I meant it is not exposed as a global that I can simply access with an extern from Rust, nor is it ever given to the library's functions. Commented Nov 18, 2019 at 14:52

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Oh interesting, I've never seen externs or structs inside functions before. Thanks!
Rust is super flexible in this aspect. You can put whatever you want in a function.
Actually, you can put whatever you want in a scope, I can mix and match fn, mod, struct, enum, union, type aliases, traits, consts, extern, static[mut], etc; with some restrictions (IE, you cannot put anything other than members inside a struct/enum/union declaration)
Four years later and I'm finally trying this, and I can't get it to work. I think the problem is github.com/rust-lang/rust/issues/54450 - glkunix_arguments[] is an array, but *const GlkUnixArgument_ expects a pointer, and so it doesn't get the correct address.
I followed the advice of that issue, and added a helper function in C to return a pointer to the array. That seems to be working.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.