Как получить размер поля структуры в 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];
}