简体   繁体   中英

How to get the size of a struct field in Rust without instantiating it

I have a structure with a byte array in it. This structure actually comes from the FFI bindings produced by bindgen, and its size is defined in C code using a macro, ie:

C code:

#define FOO_SIZE 100

struct the_struct
{
    char foo[FOO_SIZE];
    /* other fields... */
};

Generated FFI bindings:

pub struct the_struct {
    pub foo: [::std::os::raw::c_char; 100usize],
    // other fields...
}

I want to make sure that the data coming from the Rust API side fits into foo . I also do not want to hard code FOO_SIZE in my Rust API, since it is subject to change.

I understand that this can be done by instantiating the struct first, but then again, that would require explicit initialization of foo , which seems to be impossible without knowing its size. Furthermore, it is an extra step I want to avoid.

Is it possible to somehow get the size of foo statically without instantiating the structure? If not, what would be the best approach? Changing C code is not an option.

I do not know if it is possible to get the array size yet, but if you don't have too many such structures and the size doesn't change too often, I'd just declare the value explicitly:

pub const FOO_SIZE: usize = 100;

and then declare a function that will fail to compile if the hard-coded constant is wrong:

fn _assert_foo_size(s: &mut the_struct) {
    s.foo = [0; FOO_SIZE];
}

On the nightly channel I came up with this:

#![feature(raw_ref_op)]

pub struct the_struct {
    pub foo: [::std::os::raw::c_char; 100usize],
    // other fields...
}

fn main() {
    let foo_size: usize = {
        fn size<T>(_: *const T) -> usize {
            std::mem::size_of::<T>()
        }

        let null: *const the_struct = std::ptr::null();
        size(unsafe { &raw const (*null).foo })
    };

    println!("{}", foo_size);
}

As far as I can tell, &raw const (*null).foo is not UB because dereferencing a null-pointer to get another pointer is explicitly allowed. Unfortunately not only does this require the still unstable raw_ref_op feature, but because this dereferences a pointer, this also cannot be const .

This works in stable, and in const contexts starting in 1.58.

macro_rules! field_size {
    ($t:ident :: $field:ident) => {{
        let m = core::mem::MaybeUninit::<$t>::uninit();
        // According to https://doc.rust-lang.org/stable/std/ptr/macro.addr_of_mut.html#examples,
        // you can dereference an uninitialized MaybeUninit pointer in addr_of!
        // Raw pointer deref in const contexts is stabilized in 1.58:
        // https://github.com/rust-lang/rust/pull/89551
        let p = unsafe {
            core::ptr::addr_of!((*(&m as *const _ as *const $t)).$field)
        };

        const fn size_of_raw<T>(_: *const T) -> usize {
            core::mem::size_of::<T>()
        }
        size_of_raw(p)
    }};
}

pub struct Foo {
    pub foo: [u32; 34],
    // other fields...
}

// Stable as of 1.58:
const FOO_DATA_SIZE: usize = field_size!(Foo::foo);

fn main() {
    assert_eq!(field_size!(Foo::foo), 4 * 34);
}

The addr_of! macro is stable and works with raw pointers into MaybeUninit<T> , but not null pointers.

You can use closures to "simulate" evaluation of my_struct::foo without constructing it with the following:

pub struct the_struct {
    pub foo: [::std::os::raw::c_char; 100usize],
    // other fields...
}

pub fn main() {
    dbg!( get_size_of_return_type(|s: the_struct| s.foo) );
}

fn get_size_of_return_type<F, T, U>(_f: F) -> usize
where
    F: FnOnce(T) -> U
{
    std::mem::size_of::<U>()
}

Playground

This just makes rust infer the return type of the closure, U = [c_char; 100] U = [c_char; 100] given a fn(the_struct) -> U , and returns it's size.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM