DEV Community

Kyriakos Kentzoglanakis
Kyriakos Kentzoglanakis

Posted on • Updated on

A legit use case for the goto abomination

Most programming educational material mentions, at some point, the goto keyword, usually as a despicable abomination that should not exist in the face of this earth. The general consensus is that the goto statement is considered harmful and the advice is to avoid the use of goto and to restructure the code so as to eliminate the need for using it. Of course this is sound advice, intended to discourage the creation of code in which a function's control flow is heavily influenced by jumps to and from various points within its body, making the understanding, testing and debugging of the function really difficult.

Such an evil construct should surely be banished by all languages by now. However, some widely-used languages offer the goto statement in their arsenal. These include C, C++, Golang, and C#, while Java interestingly has a goto statement but it is not implemented. However, most dynamic languages (ruby, python, javascript) do not have a goto statement (although this was a fun read!).

I had never used the goto statement myself until I started writing C code for a certain kind of embedded device and encountered multiple situations where I wish I had some kind of try/catch/finally construct in the language. The purpose of the latter is to better express the need for ensuring that a bunch of statements will run no matter what especially on early-exit error paths. Now, C of course does not have such facilities but does have goto. Can we use it to handle such use cases in a C program in a way that is clean and DRY?

Let's see a (somewhat contrived) example: suppose that we have to parse a json string of the form {"name": "John"} in a C program using the well-known library cJSON. For this purpose, we define a handler function (that maybe validates and aggregates the attributes in some way - it doesn't really matter) and a process_json function that parses the json string and calls handler for each attribute that is parsed, as follows:

// return 0 for success and -1 for error
int handler(char *key, char *val) {
    // some condition gives an error
    if (val == NULL) { return -1; }
    // do something here
    return 0;
}

// process the supplied json string
// returns 0 on success, -1 on error
int process_json(const char *json, ) {
    int rc = 0;
    // here we parse the json and allocate all the resources
    // root will need to be freed further down
    cJSON *root = cJSON_Parse(json);
    // let's grab a reference to the name item
    cJSON *name = cJSON_GetObjectItem(root, "name");
    // and `handle` the inner string
    if ((rc = handler("name", cJSON_GetStringValue(name))) < 0) {
        // stop processing - we have an error
        // free the resource
        cJSON_Delete(root);
        return rc;
    }
    // happy path --  free the resource
    cJSON_Delete(root);
    return rc;
}
Enter fullscreen mode Exit fullscreen mode

Now, the above code works fine but it is a little bit repetitive since we have to free the root resource in two exit paths. There could also be more paths (e.g. for json attributes to parse) and more resources that are created in the flow and that need to be released before exit. All this repetition is error-prone and makes our function less robust and more succeptible to leaks.

Let's see how we can use the goto statement in order to concentrate all of our clean-up code in one place and reduce the likelihood of a leak. The idea is to use an end label and drive error exit paths to that label using goto statements as follows:

int process_json(const char *json) {
    int rc = 0;
    cJSON *root = cJSON_Parse(json);
    cJSON *name = cJSON_GetObjectItem(root, "name");
    if ((rc = handler("name", cJSON_GetStringValue(name))) < 0) { goto end; }

    // ... other code here / attribute parsing etc. ...

    end:
    // free the resource
    cJSON_Delete(root);
    return rc;
}
Enter fullscreen mode Exit fullscreen mode

In the above flow, we will reach the end label either by going through the whole function (happy path) or by exiting prematurely when an error occurs. This way, (a) there is no repetition, (b) we ensure that the cleanup will occur no matter what and, (c) we improve the readability of the function's flow by reducing the visual statement clutter. In short, We have a cleaner and safer function.

So, when implementing a C function with the following characteristics/constraints:

  • error conditions are handled before the happy path, and
  • one or more resources need to be cleaned up upon exit regardless of premature/error or normal exit,

then the use of goto is quite legitimate and results in cleaner, safer, DRYer code.

Top comments (0)