Interfaces and Traits in C
Posted by ibobev 2 days ago
Comments
Comment by jamesmunns 2 days ago
This isn't necessarily a negative, sometimes you actually prefer vtables and runtime polymorphism for various reasons like flexibility, or code size reasons. Just wanted to add some flavor for folks that aren't as familiar with Rust, that this isn't exactly how things usually work, as "regular" Trait usage is much more common than dyn Trait usage, which you have to explicitly opt-in to.
Comment by wasmperson 23 hours ago
- It's UB to alias one struct pointer with that of a different struct type, even if the two structs have the same first few members. Clang and GCC both exploit this for optimizations, although you can configure them not to.
- Casting function pointers is also problematic, although I think that one is more of a portability issue.
- If you want to "downcast" from a "base" struct to an "inheriting" struct, you can use the `container_of` macro, which is robust against member re-arrangement and supports multiple inheritance:
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#define container_of(p, t, f) ((t *)((char *)(0?&((t *)0)->f:p)-offsetof(t, f)))
struct reader { size_t (*read)(struct reader *self, uint8_t *p, size_t len); };
struct zeros { struct reader base; size_t total; };
size_t zeros_read(struct reader *self_, uint8_t *p, size_t len){
struct zeros *self = container_of(self_, struct zeros, base);
//...
}
- Interfaces in other languages exist to add type safety to dynamic dispatch. You get none of that in C, though, due to the casting you have to perform regardless. Code which just "does the obvious thing" using void pointers will be much simpler, making it better IMO despite the lack of type "safety": #include <stdio.h>
#include <stdint.h>
typedef size_t read_fn(void *ctx, uint8_t *p, size_t len);
size_t work(void *ctx, read_fn *read){
uint8_t buf[8];
return read(ctx, buf, sizeof buf);
}
struct zeros_reader { size_t total; };
size_t zeros_read(void *ctx, uint8_t *p, size_t len){
struct zeros_reader *self = ctx;
for(size_t i = 0; i < len; i++) p[i] = 0;
self->total += len;
return len;
}
int main(void){
struct zeros_reader z = {0};
work(&z, zeros_read);
work(&z, zeros_read);
printf("total = %zu\n", z.total);
}Comment by EPWN3D 2 days ago
But I really, really wish we could have a lightweight protocol/trait feature in C. It would remove a large source of unsafe code that has to cast back and forth between void *.