On Success and On Error Callbacks in C

This is a short post to demonstrate the use in C of a technique which is common in other languages - using callbacks as function parameters to tell the function what to do in certain circumstances. In this project I will write a function which will simulate retrieving data from a database and then call one of the two functions passed to it as pointers, the first if the data retrieval was successful and the second if there was an error.

Create a new folder and within it create an empty file called main.c. Open the file and enter or paste the following code. As this is such a short project I have listed all the code in one go. You can download it as a zip or Github if you prefer.

Source Code Links

ZIP File
GitHub

main.c

#include<stdio.h>
#include<stdlib.h>

//--------------------------------------------------------
// FUNCTION PROTOTYPES
//--------------------------------------------------------
void displaydata(char* data);
void displayerror(char* errormessage);
void getdata(void(*onsuccess)(char* data), void(*onerror)(char* errormessage));

//--------------------------------------------------------
// FUNCTION main
//--------------------------------------------------------
int main(int argc, char* argv[])
{
    puts("-------------------------------------");
    puts("| codedrome.com                     |");
    puts("| on success and on error callbacks |");
    puts("-------------------------------------\n");

    getdata(displaydata, displayerror);

    return EXIT_SUCCESS;
}

//--------------------------------------------------------
// FUNCTION getdata
//--------------------------------------------------------
void getdata(void(*onsuccess)(char*), void(*onerror)(char*))
{
    char* data = "This is your data :)";
    char* errormessage = "Something went wrong :(";

    onsuccess(data);

    onerror(errormessage);
}

//--------------------------------------------------------
// FUNCTION displaydata
//--------------------------------------------------------
void displaydata(char* data)
{
    printf("success: %s\n", data);
}

//--------------------------------------------------------
// FUNCTION displayerror
//--------------------------------------------------------
void displayerror(char* errormessage)
{
    printf("error: %s\n", errormessage);
}

Firstly we prototype three functions. The first two are displaydata and displayerror, pointers to which will be passed to the third function, getdata, to tell it what to do if the data retrieval is successful or unsuccessful.

Note the arguments to getdata: they represent pointers to functions with return types of void, and arguments of type char*. I have to admit I am not keen on the rather clumsy and contorted syntax of function pointers but unfortunately we are stuck with it!

(As with all function prototypes the names onsuccess, data, onerror and errormessage are not essential, it is only the types which are necessary. However, I always include them as well-chosen names indicate the argument's meaning when looking at the prototypes.)

The main function simply calls the getdata function with the names of the two callback functions as arguments.

The getdata function first simulates the retrieval of data and the creation of an error message by creating two strings. Of course in a real application we would attempt to read the data from a database or file, while checking for errors. We then call the two functions supplied as arguments with the simulated data and error message respectively. Obviously in the real world we would only call one or the other.

Finally we have the implementation of the displaydata and displayerror functions, which simply print out the string arguments.

We can now compile and run the program by running the following commands in your terminal.

Compile and run

gcc main.c -std=c11 -lm -o main
./main

Program output

-------------------------------------
| codedrome.com                     |
| on success and on error callbacks |
-------------------------------------

success: This is your data :)
error: Something went wrong :(

This has been a very brief demonstration of the technique, but one that can be used to simplify any code which has to interact with the "outside world", whether that be a database, files, external APIs, TCP or HTTP servers, or anything else where there is a possibility of error. The technique can also be used on purely internal code which runs in a separate thread, or which allocates dynamic memory.