9

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?

dcoles
  • 3,785
  • 2
  • 28
  • 26

1 Answers1

6

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).

dcoles
  • 3,785
  • 2
  • 28
  • 26
  • [`mem::size_of_val`](https://doc.rust-lang.org/std/mem/fn.size_of_val.html)? – trent Oct 31 '19 at 13:41
  • @trentcl Unfortunately [`mem::size_of_val`](https://doc.rust-lang.org/std/mem/fn.size_of_val.html) is not a constant function, so can't be used for const or static values (but basically it's the same function). – dcoles Oct 31 '19 at 18:25
  • Another reason `size_of_val(unsafe { &*$x.as_ptr() })` is a bad idea is that for a zero-sized array `*$x.as_ptr()` technically dereferences an invalid pointer (since the zeroth element doesn't exist). – dcoles Oct 31 '19 at 22:01
  • It's probably possible to simplify this now that [`[T]::len`](https://doc.rust-lang.org/std/primitive.slice.html#method.len) is a const function in [Rust 1.39.0](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html#more-const-fns-in-the-standard-library). – dcoles Nov 07 '19 at 18:54
  • It seems to work only if ARRAY is a const. Is it possible to get the array size for a non-constant array? It is reasonable since any array should have a fixed size known at compiling time. – Yan Zhu Aug 05 '22 at 18:57
  • 1
    @YanZhu I don't think it's possible - Rust doesn't seem to like constants that refer to non-constants, even if the desired value could just be inferred from the type. My suggestion would be adding a `const SIZE: usize = …` and then using that in your array declarations (e.g. `let array: [u8; SIZE] = …`). – dcoles Aug 06 '22 at 06:41