How to copy bytes into a c_char array?

Hi,

I cannot seem to find an easy way to copy bytes into a c_char array needed for a FFI. The following code (playground) fails to compile, because c_char is i8, but b"..." turns into u8:

fn main() {
    let c_char_array: [std::os::raw::c_char; 16] = [0; 16];
    c_char_array.copy_from_slice(&b"hello, world\0"[..]);
}

What could be an idiomatic solution?

1 Like

The most idiomatic solution without using other crates (Which I'm omitting due to my unfamiliarity with this in the crate ecosystem) would be as follows:

fn main() {
    let mut c_char_array: [std::os::raw::c_char; 16] = [0; 16];
    for (dest, src) in c_char_array.iter_mut().zip(b"hello, world\0".iter()) {
        *dest = *src as _;
    }
}

It seems you're dealing with C Strings, which Rust can deal with using std::ffi::CString.

If you want to use unsafe, perhaps the following?

let mut c_char_array: [std::os::raw::c_char; 16] = [0; 16];
unsafe {
    *(&mut c_char_array as *mut _ as *mut [u8; 13]) = *b"hello, world\0";
}

Playground

Thanks, I already thought about the iterator for loop approach, but would have guessed that there is an even easier way.

I had also tried std::ffi:{CStr,CString} before, but they also turn into [u8], so they have the same issue as my initial code.

The [c_char] buffer is part of a larger data struct in a C library that users of the C library are usually initialise by copying bytes into via memcpy and friends.

mind that this is architecture dependent; for example on ARM and RISC-V c_char is u8, which makes them finicky to work with

Thanks, I already thought about the iterator for loop approach, but would have guessed that there is an even easier way.

a loop is easiest and most portable in this case, i think

Since that's the case, I'd also suggest noting the importance of using as _ instead of as i8 when using a loop.

For example:

#[cgf("windows")]
type Foo = i8;
#[cfg("unix")]
type Foo = u8;

fn foo(x: char) -> Foo {
    x as i8
}

Would compile on windows but not on macos or linux.
If we instead said either:

fn foo(x: char) -> Foo {
    x as _
}

Or

fn foo(x: char) -> Foo {
    x as Foo
}

Then it would be completely portable.

I personally go the other way around, I interpret c_chars as u8s, given how most of the Rust APIs use u8s (such as CStr and stuff):

fn main ()
{
    let mut c_char_array: [u8; 13] = [0; 13];
    c_char_array
        .copy_from_slice(&b"hello, world\0")
    ;
}

The other option is to use a little bit of unsafe code, to get the valid
&mut [c_char] -> &mut [u8]
cast:

fn main ()
{
    let mut c_char_array: [c_char; 14] = [0; 14];
    c_char_array
        .as_mut_bytes()
        .copy_from_slice(&b"Hello, World!\0"[..])
    ;
}

The unsafe code can either be written directly (Playground), or used through a helper crate, such as
::safe_transmute::to_bytes::transmute_to_bytes_mut::<c_char>()

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.