Any suggestions to better handle dynamically sized types for ffi use cases?

many ffi apis contains this common pattern: a struct fixed header fields and a variable length array of payload data, and the size of the payload is determined at runtime, something like:

typedef struct {
	// other fields omitted
	DWORD count;
	SHORT data[1];
} FooList;

/// pass NULL for buffer to retrive required buffer size
BOOL GetFooList(__OUT __OPT FooList *buffer, __INOUT DWORD *buffer_size);

I would like to write

#[repr(C)]
struct FooList {
	pub header: FooHeader,
	pub data: [u16],
}

but the only way I know to get a Box<FooList> is to coerce from types with statically known size like:

// this is for illustration only,
// to actually use `UnsizeCoerce`, you need the unsized and sized types both use the same generic type definition
#[repr(C)]
struct FooListKnownSize<const ARRAY_SIZE: usize>  {
	pub header: FooHeader,
	pub data: [u16; ARRAY_SIZE],
}

I don't know how (or if) you can legitimately allocate a Box<FooList> with runtime queried size requirement.

currently I just allocate raw bytes (with alignment) and reinterpret the raw bytes at calculated offset, like this:

struct FooHeader {
	// other fields omitted
	pub count: u32,
}

#[repr(transparent)]
struct FooListRaw([u8]);

// one possible alternative using `alloc`, but the idea is the same
// struct FooListRaw(*mut u8);

const ALIGNMENT: usize = 4;

impl FooListRaw {
	pub fn header_mut(&mut self) -> &mut FooHeader {
		// SAFETY:
		// cast aligned pointer to the first field of a repr(C) struct
		unsafe {
			let (_prefix, header, _suffix) = self.0.align_to_mut::<FooHeader>();
			&mut header[0]
		}
	}
	pub fn data_mut(&mut self) -> &mut [u16] {
		// SAFETY: ???
		unsafe {
			let ptr = self.0.as_mut_ptr();
			// CAUTION: may need to take into account additional paddings
			let offset = ptr.align_offset(ALIGNMENT) + size_of::<FooHeader>();
			let len = self.header_mut().count as usize;
			slice::from_raw_parts_mut(ptr.add(offset).cast(), len)
		}
	}
}

impl FooListRaw {
	fn zeroed(nbytes: usize) -> Box<Self> {
		let raw_storage = vec![0u8; nbytes + ALIGNMENT].into_boxed_slice();
		// SAFETY: ???
		// give `repr(transparent)` struct with single field, are we allowed to cast between fat boxes?
		unsafe { transmute(raw_storage) }
	}
	/// SAFETY: do you trust the ffi api?
	pub unsafe fn new() -> Option<Box<Self>> {
		let mut len = 0;
		// query size requirement first
		if !ffi_get_foo_list(null_mut(), &mut len) {
			return None;
		}
		// allocate
		let mut raw = Self::zeroed(len as usize);
		// initialize
		if !ffi_get_foo_list(raw.header_mut(), &mut len) {
			return None;
		}
		Some(raw)
	}
}

this is bothering me for quite long, I'm always worried about the safety, as it's so delicate, and I have a lot of such types. so my question is:

is there obvious UB using this approach, and do you guys have better suggestions?

thanks in advance.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.