From the point of view of memory safety, there isn't a "best practice" answer that is best for every situation. The whole point of writing in C is that you can choose how to manage your memory instead of doing it the same way all the time.
Coming back to your question, the two versions are pretty similar in that the caller code is ultimately responsible for the memory (initially allocating and ultimately releasing it), as opposed to having your resource be statically allocated of belonging to some global pool or whatever. They also aren't really mutually exclusive. Consider the following alternative:
//functions exported from module A
struct Data * allocate_data();
void initialize_data(Data *);
void deinitialize_data(Data *);
void deallocate_data(Data *);
//module B:
int main(void){
Data * allocate_data();
initialize_data(data);
//do stuff
deinitialize_data(data);
deallocate_data(data);
}
In this version we encapsulate the Data behind an abstract data type. A single module implements all operation on the Data datatype, including initialization, alllocation and deallocation and the caller module is responsible for actually choosing when to allocate and deallocate things. The caller module only ever uses pointer to the datatype and never uses the Data struct directly 1
From this point we can get to your first solution by adding an extra convenience functions to allocate and initialize at the same time. This is most useful if you usually want to malloc your data instead of allocating it in the stack and has the extra advantage of making it easier to code complicated initializations.
To get to the second version, its just a matter of making the Data struct part of the public interface. The advantage is that the caller code allocate that struct on the stack rn statically and the disadvantage is that if the caller does that he is responsible for all the allocation logic (for example, making sure that the array size of the allocated memory is the same array size passed to the initialization function).
1 We could enforce this by doing an empty struct Data;
declaration in the Data.h
header and including the actual struct Data { char * payload; }
declaration in the Data.c
file.