While reading a data structure book, I have implemented a chained hash table. I want to share the code with you and get your opinion about it.
chained_hash_table.h
#ifndef CHAINED_HASH_TABLE_H_INCLUDED
#define CHAINED_HASH_TABLE_H_INCLUDED
#include <stdlib.h>
#include "../linked_list/linked_list.h"
typedef size_t hkey_t;
typedef struct ChainedHashTable_ {
    size_t buckets;
    List **table;
    hkey_t (*hash)(const void *key);
    void (*destroy)(void *data);
    int (*compare)(const void *fdata, const void *sdata);
}ChainedHashTable;
ChainedHashTable *chtb_init(size_t buckets,
        hkey_t (*hash)(const void *key),
        void (*destroy)(void *data),
        int (*compare)(const void *fdata, const void *sdata));
void chtb_destroy(ChainedHashTable *chtb);
int chtb_insert(ChainedHashTable *chtb, void *data);
int chtb_remove(ChainedHashTable *chtb, void **data);
int chtb_lookup(const ChainedHashTable *chtb, const void *data);
#endif
chained_hash_table.c
#include <string.h>
#include "chained_hash_table.h"
#define chtb_hash_key(chtb, data)   chtb->hash(data) % chtb->buckets
ChainedHashTable *chtb_init(size_t buckets,
        hkey_t (*hash)(const void *key),
        void (*destroy)(void *data),
        int (*compare)(const void *fdata, const void *sdata)) {
    ChainedHashTable *chtb;
    if( (chtb = malloc(sizeof *chtb)) != NULL ) {
        chtb->buckets = buckets;
        chtb->hash = hash;
        chtb->destroy = destroy;
        chtb->compare = compare;
        if( (chtb->table = malloc(buckets * sizeof *chtb->table)) == NULL) {
            free(chtb);
            return NULL;
        }
        int i;
        for(i = 0; i < buckets; ++i) {
            if( (chtb->table[i] = list_init(destroy, compare)) == NULL) {
                for(--i; i >= 0; --i) 
                    free(chtb->table[i]);
                free(chtb);
                return NULL;
            }
        }
    }
    return chtb;
}
void chtb_destroy(ChainedHashTable *chtb) {
    size_t i;
    for(i = 0; i < chtb->buckets; ++i)
        if(chtb->table[i] != NULL)
            list_destroy(chtb->table[i]);
    free(chtb->table);
    free(chtb);
}
int chtb_insert(ChainedHashTable *chtb, void *data) {
    size_t bucket = chtb_hash_key(chtb, data); 
    if(list_find(chtb->table[bucket], data) != NULL)
        return 1;
    if(list_prepend(chtb->table[bucket], data) != 0)
        return -1;
    return 0;
}
int chtb_remove(ChainedHashTable *chtb, void **data) {
    size_t bucket = chtb_hash_key(chtb, data); 
    if(chtb->table[bucket] == NULL)
        return -1;
    return list_remove(chtb->table[bucket], data);
}
int chtb_lookup(const ChainedHashTable *chtb, const void *data) {
    size_t bucket = chtb_hash_key(chtb, data); 
    return list_find(chtb->table[bucket], data) == NULL ? 0 : 1;
}


