I have a C library that expects string type that explicitly defines the string length:
#[repr(C)]
pub struct FFIStr {
len: usize,
data: *const u8,
}
Because this type is used as a static, I'd like a way to safely declare it using a const function or macro (instead of manually setting len
).
My first attempt was to use a macro and len()
, however in versions before 1.39.0, it is not possible to get the length of a slice as a const fn:
macro_rules! ffi_string {
($x:expr) => {
FFIStr { len: $x.len(), data: $x as *const u8 }
};
}
#[no_mangle]
pub static mut HELLO_WORLD: FFIStr = ffi_string!(b"Hello, world!");
error: core::slice::<impl [T]>::len` is not yet stable as a const function
My second attempt was to use std::mem::size_of<T>
, but there doesn't appear to be a way to get the type of the static array short of using generics:
const fn ffi_string<T>(s: &'static T) -> FFIStr {
FFIStr { len: ::std::mem::size_of::<T>(), data: s as *const _ as *const _ }
}
#[no_mangle]
pub static mut HELLO_WORLD: FFIStr = ffi_string(b"Hello, world!");
While this works (surprisingly), it's horribly prone to misuse as it wildly casts whatever you pass it to a *const u8
.
It seems like const_generics would be a nice solution to this, but they're currently unstable:
const fn ffi_string<const SIZE: usize>(s: &'static [u8; SIZE]) -> FFIStr {
FFIStr { len: SIZE, data: s as *const u8 }
}
#[no_mangle]
pub static mut X: FFIStr = ffi_string(b"Hello, world!");
error[E0658]: const generics are unstable
Is there a better way of determining the size of a static array at compile time?
In Rust 1.39.0 [T]::len
was stabilised as a const function, now making this straight forward:
const ARRAY: [i32; 3] = [1, 2, 3];
const ARRAY_SIZE: usize = ARRAY.len();
fn main() {
assert_eq!(3, ARRAY_SIZE);
}
In earlier versions of Rust, here's one way based on the common C ARRAY_SIZE
macro:
macro_rules! array_size {
($x:expr) => (
(size_of_val($x) / size_of_val(&$x[0]))
)
}
const fn size_of_val<T>(_: &T) -> usize {
std::mem::size_of::<T>()
}
fn main() {
assert_eq!(3, array_size!(&[1, 2, 3]));
assert_eq!(13, array_size!(b"Hello, world!"));
}
It uses a const generic function size_of_val<T>
to determine the type and thus the size of a value passed by reference (the built-in std::mem::size_of_val
isn't const).
Note: This doesn't work for arrays of size 0. This can be fixed by using size_of_val($x) / size_of_val(unsafe { &*$x.as_ptr() })
at the cost of wrongly accepting non-array types (e.g. &String
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With