Putting aside the relative merits of your approach, there are several things about your code that could do with improvement.
Your header file is essentially the public interface to your code. One of the things that bugs me about header files is function prototypes that contain no parameter names. They tell you nothing about what the parameters are use for / mean. Compare:
void insert(Word*, Definition*, Dictionary*);
void insert(Word *wordToInsert, Definition *wordDefinition, Dictionary *destination);
Giving the parameters names helps to document the expectation of the functions and saves the developer having to read documentation comments (which your header doesn't contain), refer to the documentation (which you may or may not of written), or read the code of the function they're calling and figure it out. The lower the barrier of entry, the more likely people are to use your code rather than finding an alternate implementation / writing their own.
malloc can fail
You're not checking the return codes from any of your
malloc calls. It's perfectly possible for it to return null. You should be checking it's value and behaving sensibly where possible, particularly if you're writing code that might end up in a library.
malloc isn't calloc
createDictionary allocates memory for pointers to the word definitions:
d->array = malloc(WORDS * sizeof(Definition*));
malloc doesn't automatically zero the memory. Since you're allocating pointers, there is a good chance that any clients will assume that non-null pointers are actually present. You should be zeroing the pointers by either using
calloc, or calling
Be safe with pointers
insert doesn't check to see if a definition already exists, it simply assigns into the array. This means that any previous definition / pointer is lost. This may lead to memory leaks if called twice for the same word.
Be clear about pointer ownership
createXXX methods all use
malloc to allocate memory for the objects returned. When you call
insert you pass in three pointers (word,definition,dictionary). As a client of the method, I think it would be reasonable to assume that by calling
insert I was passing responsibility for the pointers to the dictionary. This is the case for the definition, which is inserted into
array member of the dictionary. The word however is only used to calculate the hashCode, so is consequently not stored anywhere. This is confusing and is liable to result in memory leaks.
Provide a cleanup mechanism
You don't have any cleanup functionality. This may be acceptable for you, but I'd consider adding a method
destroyDictionary, that cleans up the dictionary and any pointers that it owns.
It's standard practice to use header guards to prevent multiple definition errors (and pointless include cycles). Typically you'd wrap the entire contents of the header in something like (guard name styles can vary, however will usually be related to the name of the file):
/* Rest of header code */
Alternately, depending on your compiler support you may want to use:
N.B. Out of historic habit, I tend to use header guards that have leading and trailing underscores. As pointed by @Emily this isn't an approved practice, so you should exclude them from your own guards. Technically, using underscore prefixed names is reserved and hence could cause naming conflicts. That said, across many different compilers and environments, I've never encountered any conflicts caused by header guards and the risk can be largely mitigated through include order.
This is somewhat subjective, however I tend to include files in a particular order:
#include <standard headers>
#include <external library headers>
#include <internal library headers>
#include <project headers>
This tends to mean that if you do end up with any kind of conflicts with existing headers that the errors are more likely to be flagged up in your projects header where the issue is probably caused, rather than in the standard library. This is the reverse of the order that you've included them in your 'main.c'.