Subject: Re: [PATCH 01/10] ivec: introduce the C side of ivec

From: Phillip Wood <[email-masked]>
Date: Fri, 16 Jan 2026 10:39:28 +0000

I've Cc'd Peff and René for a second opinion if you have time please.

On 15/01/2026 15:55, Ezekiel Newren wrote:
> On Thu, Jan 8, 2026 at 7:34 AM Phillip Wood <[email-masked]> wrote:
 >
>>> +static void _set_capacity(void *self_, size_t new_capacity)
>>> +{
>>> +     struct IVec_c_void *self = self_;
>>
>> Passing any of the ivec variants defined below to this function invokes
>> undefined behavior because we're not casting the pointer back to the
>> orginal type. However I think on the platforms we care about
>> sizeof(void*) == sizeof(T*) for all T so maybe we can look the other way.
> 
> If someone finds that this code does not work because of this
> assumption I'd like to know. But I can't fathom a case where it
> wouldn't work.

So we have two different structs

struct IVec_c_void {
	void *ptr;
	size_t length;
	size_t capacity;
	size_t element_size;
}

and

struct Ivec_u8 {
	uint8_t *ptr;
	size_t length;
	size_t capacity;
	size_t element_size;
}

One the platforms we care about they will have the same memory layout as 
all pointers have the same representation. However I don't think they 
are "compatible types" in the language of the C standard because the 
type of the "ptr" member differs. That means casting IVec_u8* to 
IVec_c_void* either directly or via void* is undefined and so

	struct IVec_u8 vec;
	ivec_init(&vec, sizeof(*vec.ptr));

is undefined. For the compiler to see the undefined cast it needs to 
look across translation units because the implementation of ivec_init() 
will be in a separate file to where it is called. Maybe that and the 
fact they have the same memory layout saves us from having to worry too 
much though I'm always nervous of undefined behavior.

An alternative would be to pass the individual struct members as 
function parameters

	void ivec_init(void **vec, size_t &length, size_t &capacity,
		       size_t &element_size_, size_t element_size)
	{
		*vec = NULL;
		*length = 0;
		*capacity = 0;
		*element_size_ = element_size;
	}

and have DEFINE_IVEC_TYPE create typesafe wrappers

	static inline void ivec_u8_init(struct IVec_u8 *vec)
	{
		void *ptr = vec->ptr;
		ivec_init(&ptr, &v->length, &v->capacity,
			  &v->element_size, sizeof(*(v->ptr));
		vec->ptr = ptr;
	}

That's safe because we cast the "ptr" member to "void*" and then back to 
the original type. On the rust side the implementation of IVec<T> would 
also need to split out the individual struct members when it calls 
ivec_init() etc. It's all a bit more effort but the benefit is that we 
don't have any undefined behavior and we have a nice typesafe C 
interface to 'struct IVec_*'.

Thanks

Phillip