Wouldn't it be preferable to write only a forward declaration of the struct in the header files, and define the structs themselves at the .c files? In other words, wouldn't it be better to define them as Abstract Data Types (ADT) ?
That way, users of the library would have no direct access to the fields of the struct (e.g. hashtable->num_buckets;), they'd have to use a function declared at the correspondent header file. (e.g. get_num_buckets(hashtable);).
Direct access to member fields could break the data structure because data structures always needs to follow certain rules in insertion and deletion.
That's not possible - the compiler needs to know the size so that it can allocate space for the structure.
You can get away with forward declarations of things if you only ever need a pointer to the struct, because the compiler knows how big a pointer is and only needs to know the size of the structure when you dereference it.
You also need to know the size when you allocate it - whether you're using malloc, or allocating statically. As you want to leave control of allocation up to the caller, then the caller needs to know the size.
Theoretically I suppose you could allocate through a macro that used an explicit size number in the header, but going to those lengths to take the struct out of the header seems a bit extreme.
That way, users of the library would have no direct access to the fields of the struct (e.g. hashtable->num_buckets;), they'd have to use a function declared at the correspondent header file. (e.g. get_num_buckets(hashtable);).
Direct access to member fields could break the data structure because data structures always needs to follow certain rules in insertion and deletion.