Как получить размер поля структуры в Rust без его создания

У меня есть структура с байтовым массивом. Эта структура фактически происходит из привязок FFI, созданных bindgen, и ее размер определяется в коде C с помощью макроса, то есть:

Код C:

#define FOO_SIZE 100

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

Сгенерированные привязки FFI:

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

Я хочу убедиться, что данные, поступающие со стороны Rust API, подходят для foo. Я также не хочу жестко кодироватьFOO_SIZE в моем Rust API, поскольку он может быть изменен.

Я понимаю, что это можно сделать, сначала создав экземпляр структуры, но опять же, это потребует явной инициализации foo, что кажется невозможным, не зная его размера. Кроме того, я хочу избежать этого дополнительного шага.

Можно ли как-нибудь получить размер fooстатически без создания экземпляра структуры? Если нет, что было бы лучше всего? Изменить код C нельзя.

4 ответа

Решение

На ночном канале я придумал вот что:

#![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);
}

Насколько я могу судить, &raw const (*null).fooне является UB, потому что явно разрешено разыменование нулевого указателя для получения другого указателя. К сожалению, это требует не только все еще нестабильногоraw_ref_op функция, но поскольку это разыменовывает указатель, это также не может быть const.

Вы можете использовать замыкания для «симуляции» оценки my_struct::fooбез построения его со следующим:

      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>()
}

Детская площадка

Это просто заставляет ржавчину сделать вывод о возвращаемом типе замыкания, U = [c_char; 100]учитывая fn(the_struct) -> Uи возвращает его размер.

Это работает в стабильной и в constконтексты, начиная с 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!макрос стабилен и работает с необработанными указателями в MaybeUninit<T>, но не нулевые указатели.

Я не знаю, можно ли еще получить размер массива, но если у вас не слишком много таких структур и размер не меняется слишком часто, я бы просто объявил значение явно:

pub const FOO_SIZE: usize = 100;

а затем объявить функцию, которая не сможет скомпилировать, если жестко заданная константа неверна:

fn _assert_foo_size(s: &mut the_struct) {
    s.foo = [0; FOO_SIZE];
}
Другие вопросы по тегам