Initializing C string from Rust for FFI


#1

Hi there!

I’m currently creating an interface to a C library that has an structure with a field of char name[11];, which I have implemented in Rust as name: [c_char; 11]. I hope is OK. The thing is that I need to create some instances of the struct as constants, which in C is pretty easy:

const NtruEncParams EES401EP1 = {
    "EES401EP1",   /* name */
    // Other fields       
};

which I don’t know how to implement. Of course, I have tried the following without success:

const EES677EP1: NtruEncParams = NtruEncParams {
    name: "EES677EP1",
    // Other fields
};

I get the following:

expected [i8; 11],
found &'static str
(expected array of 11 elements,
found &-ptr) [E0308]

Which would be the best option to do it in Rust?


#2

So, there’s a few things here. One is that, as you’ve seen, string literal syntax gives you a &'static str rather than a [c_char; 11].

You can use a byte string to get a &'static [u8; N] with the length: b"EES677EP1". Doing that:

extern crate libc;

use libc::c_char;

struct NtruEncParams {
    name: [c_char; 11],
}

const EES677EP1: NtruEncParams = NtruEncParams {
    name: b"EES677EP1",
    // Other fields
};

gives another error:

error: mismatched types:
 expected `[i8; 11]`,
    found `&'static [u8; 9]`
(expected array of 11 elements,
    found &-ptr) [E0308]

Two things are wrong: you have only a 9-length array, and, it’s &'static rather than by value. Since you didn’t include the NtruEncParams definition, I’m not sure, but you could

struct NtruEncParams {
    name: &'static [c_char; 11],
}

Which fixes the &'static issue, and

name: b"EES677EP1\0\0",

Which fixes two things: first, that C strings are null terminated, but &strs aren’t, and second, the length. But this has another error:

error: mismatched types:
 expected `&'static [i8; 11]`,
    found `&'static [u8; 11]`
(expected i8,
    found u8) [E0308]

… and that is where I’ll let someone else step in, as I’m not sure what the best thing to do here is.


#3

From my limited experience in C, a char[11] actually only has room for 10 characters because the last byte needs to be a NUL terminator. C string literals include this implicitly.

@steveklabnik Unfortunately, using a Rust slice in a C struct is a bad idea. The C API is not expecting a fat pointer, it’s expecting an array inlined into the struct.

This will necessitate some copying.


let mut chars = [0i8; 11];

let my_string = b"EES677EP1";

// Make sure we're not truncating.
// A more sophisticated check could truncate on the last valid code point that will fit.
assert!(my_string.len() < 11);

// You could use ptr::copy_nonoverlapping() as well, but this should
// optimize down to something like it anyway.
// Slicing at 10 makes sure we're not overwriting the NUL terminator.
for (left, right) in chars[..10].iter_mut().zip(my_string) {
    *left = *right as i8;
}

let enc_params = NtruEncParams {
    name: chars,
};

// Do FFI stuff here.

Unfortunately, I don’t know how to do this cleanly in a const without a compiler plugin.


#4

Thanks to both of you :slight_smile: I’m using hand-written constants until someone figures out how to do it properly :smile:


#5

I have a macro in the c_string crate that facilitates creation of C-format static strings from Rust string literals. It only works for UTF-8 input literals, though, because of a limitation of concat!. I never got around to write a syntax extension to concatenate bytestring literals.

Much of that library is outdated and misguided, but the macro can be helpful, feel free to borrow it (under Rust-like licensing terms). Note that it depends on libc::c_char being reexported in the defining crate. For in-crate purposes, you can just replace $crate with libc.


#6

Thanks!

I see that that macro would create a std::ffi::c_str::CStr. Can I easily convert that to a [c_char; 11]?


#7

If you want a [c_char; 11] value, converting to that means copying bytes into it. If that comes by reference, I don’t remember how references are coerced between DSTs and fixed-size arrays, but there should be a way. CStr has method to_bytes() to get the DST reference, but there’s a twist: in the future, the implementation can be changed to actually scan the string for the terminating NUL. So if your program does it too often, you might want a macro to create &'static [u8] constants from NUL-appended literals.


#8

I should have noted that a coercion from a slice to a fixed-size array reference would be unsafe: you need to ascertain that the slice has the necessary number of elements.