From: Ezekiel Newren Date: Sat, 17 Jan 2026 16:04:22 GMT Subject: Re: [PATCH 01/10] ivec: introduce the C side of ivec Message-ID: In-Reply-To: On Sat, Jan 17, 2026 at 6:55 AM Phillip Wood wrote: > > On 16/01/2026 20:19, René Scharfe wrote: > > On 1/16/26 11:39 AM, Phillip Wood wrote: > >> 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 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. > > > > True. The GCC docs give a fun example of what a compiler might do > > when using different struct types to access the same memory: > > > > https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Aliasing-Type-Rules.html > > Thanks for the link > > > Not sure it applies to this case, but the point is that compilers > > can and will do terrifying things when they smell UB, with little > > concern for safety or original intent. > > > >> 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; > >> } > > > > The ampersands (&) should be asterisks (*), right? > > Indeed, that's embarrassing - I must have been thinking of the caller. > > >> 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; > >> } > > > > Mixes "v" and "vec", misses a closing parenthesis. Looks viable, > > though, and this method should be applicable to the rest of the > > functions as well (on the C side). > > > > I guess this doesn't require an element_size member anymore as > > each wrapper can pass in the sizeof value. > > Good point > > >> 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 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_*'. > > Right. No idea how ugly this would be on the Rust side, though. > > I'm hoping it's not too bad and `impl IVec` just contains the > equivalent of the wrappers generated by DEFINE_IVEC_TYPE() > > Thanks > > Phillip > > > > René > > > I don't like this solution. ivec_push() is the only function that deals with actual values. The rest are just generic memory management functions. What if we used: #define ivec_init(vec) { \ (vec)->ptr = NULL; \ (vec)->length = 0; \ (vec)->capacity = 0; \ (vec)->element_size = sizeof(*(vec)->ptr); \ } #define ivec_push_unsafe(vec, value) (vec)->ptr[(vec)->length++] = (value) /* * grow by at least 1 */ #define ivec_push(vec, value) { \ if ((vec)->length == (vec)->capacity) \ ivec_reserve(vec, 1); \ ivec_push_unsafe(vec, value); \ } Instead of concrete functions?