[FFI] Sized array and coersion

Working with my code I came across something that is bothering me. I have a null-terminated string stored in a fixed-length array, [i8; 255], but the function that converts it to a string only accepts &[u8].

CStr

I saw that there is a way in CString to use a Vec to convert but the function is still unstable and requires a Vec, if possible I wanted to do this without resorting to such complexity.

The problem is that no matter what conversion I do I can't convert a sized array to an unsized array and I haven't found anything on the internet about it.
Is there a way Rust allows the function to accept my buffer?

I’m not entirely sure what you mean by “unsized array”, but if you want to call the from_bytes_with_nul function, just pass it a reference to your fixed-length array. Rust can automatically (and implicitly) convert a &[u8; 255] to a &[u8]. The type [u8] (or the type &[u8], depending on who you ask) is called a slice.

Example code in the playground

1 Like

Simply passing a reference did not solve the problem. "unsized" was precisely the term used by the compiler when pointing out the error.
But I will try your approach here.

Hmm, in case you’re trying to understand and/or resolve a particular compiler error it might help if you shared the error message.

Regarding the term “unsized” Rust has so-called “unsized types” (or “dynamically sized types”, DSTs) of which the slice type [u8] (or more generally [T]) is an example. At a first approximation, such types don’t exist at all but are just a trick to use the &-symbol in things like slice types like &[u8] or trait object types such as e.g. &dyn Any. More precisely, unsized types can only exist behind a reference, which includes &, but also &mut, Box<> or Arc<> for example. Something like Box<dyn Error> is a common example using a different pointer type (in this case Box), but Box<[u8]> is also possible.

In the case of a slice &[u8], the reference-type &[u8] consists of a pointer to some data which can belong for example to an array as in your case, or to a Vec, etc, plus—additionally—the length of the slice. So &[u8] internally is like a pair (*const u8, usize) of a pointer and a length. This makes the &[u8] references twice as large as ordinary references; for this reason these are also called “fat pointers”. Then there are coercions in place that can create slices like &[u8] from other reference types such as &[u8; 42] or &Vec<u8>. These coercions are build-in for arrays, or reslized through the Deref trait and based on unsafe API such as slice::from_raw_parts, like is the case for Vec.

Oh, and by the way, the term “array” in Rust always refers to types [T; N] of known, fixed length.

1 Like

Sorry for the vague initial post.
I never got along very well with slices and this is draining my energy.
I'll correct myself here, I mixed the terms of Rust and C.

The problem is:

  1. The C buffer is of the char type, in Rust it was represented as i8 but the function uses u8.
error[E0308]: mismatched types
  --> tests/vulkan_loader.rs:19:57
   |
19 |             let s = std::ffi::CStr::from_bytes_with_nul(&layer.layerName).unwrap().to_str().unwrap();
   |                                                         ^^^^^^^^^^^^^^^^ expected slice `[u8]`, found array `[i8; 256]`
   |
   = note: expected reference `&[u8]`
              found reference `&[i8; 256]`
  1. If I try to convert to & [u8] I can't, it's a conversion that doesn't involve primitive types.
error[E0605]: non-primitive cast: `&[i8; 256]` as `&[u8]`
  --> tests/vulkan_loader.rs:19:57
   |
19 |             let s = std::ffi::CStr::from_bytes_with_nul(&layer.layerName as &[u8]).unwrap().to_str().unwrap();
   |                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object

What I would like to know is how to make the function accept the buffer returned to me in the safest way possible.

OH, I didn’t even notice the i8 vs u8 in your original post......

You can... well... use unsafe code or crates such as bytemuck to convert i8 slices to u8 slices. Look at this playground: Rust Playground

1 Like

On a second thought, it looks like you’re trying to get a str out of this in the end anyways. Which sounds like the [i8; 256] buffer might not actually be a string of length 255 but potentially shorter (with a 0 byte in the middle). CStr::from_bytes_with_nul will actually panic in this case. If this is the case, let’s hope that the code that got you the [i8; 256] in the first place is sound in that that array doesn’t actually contain any uninitialized data. Assuming it is sound, you would still need to split at the first 0 byte manually (unless there’s a nice crate or function that can help us that I don’t know of). Given that it’s i8s, does the string actually contain any non-ASCII characters and/or do you need to validate that? Anyways, you can build str from u8 of ASCII (or more generally Utf8-encoded) slices directly without using CStr. For searching the u8 byte, the bstr crate might be useful. Here’s a version that does not check for non-ASCII characters and supports ASCII or Utf8 even if the latter is weird with signed i8s: Rust Playground.

2 Likes

If it's i8, you will have to cast it anyway. That's because c_char can be either i8 or u8, depending on the target platform.

let arr: [i8; 255] = …;
let cstr = unsafe { CStr::from_ptr(arr.as_ptr().cast()) };
1 Like