Signalling (throwing) and Handling (catching) Custom Conditions (exceptions) in R Ⅰ : Introduction

Like many other programming languages, R provides constructs to handle unusual conditions that may break the flow of the script. But R being R, it does it in its on peculiar way (or, more accurately, does it in a number of ways as we will later see). Actually, the R condition system does have a number of interesting but not so well documented features one being that you can actually create custom catchable conditions instead of simply signalling generic errors (-ie : going beyond stop("something bad happened")).

In the following short series, we’ll take a look at R‘s way to handle (catch) exceptions (conditions). But, before that, we need to consider condition handling in a broader perspective. Of course, if you’re familiar with this you can skip this part and go directly to the second part.

Coping with Disaster

Per Murphy’s law, anything that can go wrong will go wrong and computer programming is no exception.

In order to give the desired output, a program needs a number of requirements to be met. If it has to process a file, the file needs to exist. If the program cannot allocate memory when it needs it, it cannot go on. If a program writes a value into an array beyond it may trigger a segmentation fault. Also, functions usually only work with arguments in a certain domain (sqrt() expects a non negative float).

All those peculiar situations will somehow break the intended flow of the program. In other circumstances, the program goes on but yields an unsuitable outcome which is likely to break subsequent computations such as a NaN1.

This why we need we need ways to signal and recover from those unwanted but inevitable situations.

Returns Codes

The crudest way of signalling a situation is probably the return code (rc). This is commonly used in languages that, like C, do not provide dedicated mecanisms for handling conditions. In this case, a function returns a value indicating an error instead of the desired outut. This can be an integer or a NULL value (like malloc()).

But there are a number of issues with this.

First, rc cause the semipredicate problem in which the signalling of failure uses an otherwise valid return value. In other words, a function often cannot return an integer code and data. Also, rc do not give information about the context of the error.

With rc, error handling solely rely on developers which, itself, is error prone. Error checking is tedious and may result in code bloats since pretty much everything you do needs to be nested in if blocks. And you need to make sure your code handle the rc of each function you call2 which incurs runtime overhead since the code is executed whether an error has occurred or not.

You also need to make sure you free() the dynamically allocated memory, close() file handles, release mutexes…and every single resources along the way when unwinding the call stack and pray that the code you rely on but did not wrote does that too. Indeed, when using a hierarchy of libraries, an underlying lib may fail to handle or propagate the error (or do it in its own peculiar way).

As stated in Roberts (1989)3 and elsewhere, lack of build–in support for error handling makes errors too easy to ignore.

Exceptions Handling

As soon as the 1960, languages such as Lisp and Lisp dialect or PL/I began to integrate exception handling into their control structure making error handling a feature of the language (and not something you have to make up or bare from other developers). But those constructs only saw wide adoption from the 1980’s onward in languages such as Ada, Modula-2+, C++, Eiffel,….

An exception is a data structure that provides information about the condition that gave rise to it (sort of speak). Exceptions are usually thrown (or raised). A thrown exception alters the flow of the program in a way that makes it impossible to ignore (otherwise the program will simply abort). This why exception comes with statements that controls the flow of the program in order to handle the exception thus allowing for recovering from the condition. Kinda like a return statement that allows for non local jumps (the function may not return within the calling function but higher in the call stack)4.

Many languages now implements exceptions and, as such, there are many syntaxes. And, beyond syntax, there are semantics and program behaviour as we will see in the next part. And, as a side note, there’s also no real consensus on what actually constitutes an exception between programming languages and also languages users.

But many language use the try–catch syntax, one notorious example being C++.

A C++ example

For the sake of the example, say we have a C++ processFile() function that processes a single file. Of course, this function has to check that this file exists before further processing and therefore throws a fileNotFound exception when this condition occurs. In order to do this, we first need to create a fileNotFound exception which a class that inherits from the exception class.

Now say we have a function processFiles() that simply loops over a file name array to process multiple files with the processFile() function. If the file cannot be found, we still want to be able to recover from this condition and move on to the next file. Therefore, the processFile() call is encapsulated in a try block. If a fileNotFound exception is raised it will be caught by the catch block, processFiles() prints a warning and resumes to the normal flow of processFiles() moving on to process the next file.

An implementation in C++ may look like this :

#include <iostream>
#include <string>
#include <sys/stat.h>
// util that checks if a file exists
// https://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exists-using-standard-c-c11-14-17-c
// C++17 has std::filesystem::exists();
inline bool fileExists (const std::string& name) {
  struct stat buffer;   
  return (stat(name.c_str(), &buffer) == 0); 
}
// The fileNotFound exception
class fileNotFound : public std::exception {
  private:
    const std::string fileName;
  public:
    // constructors
    fileNotFound(std::string  msg) : fileName(msg) {}
    fileNotFound(char        *msg) : fileName(msg) {}
    //
    std::string what() {
      return "file " + fileName + " not found.";
    }
    // accessors
    std::string getFileName(){
      return fileName;
    }
};
//
void processFile(const std::string fileName){
  std::cout << "processFile: processing " << fileName << ".\n";
  if( !fileExists(fileName) ) throw fileNotFound(fileName);
}
// Optionally to avoid function name mangling: 
extern "C" void processFiles();
//
void processFiles(){
  //
  const std::string fileNames[] = {"file0.txt", "file1.txt", "file2.txt"};
  //
  for(const std::string &fileName : fileNames){
    //
    try{
      processFile(fileName);
    }
    catch ( fileNotFound& e ){ 
      // if fileNotFound exception is throw within the try block, we end up here
      std::cout << "processFiles: WARNING " << "file " << e.getFileName() << " not found." << "\n";
    } 
    catch ( std::exception& e ){
      // if any other exception than fileNotFound is thrown, we end up here
      // print a message
      std::cout << "processFiles: exception thrown while processing " << fileName << "file" << "\n";
      std::cout << e.what() << "\n";
      // and rethrow
      throw; 
    } 
  }
}

Compiling and executing yields :


processFile: processing file0.txt.
processFiles: WARNING file file0.txt not found.
processFile: processing file1.txt.
processFiles: WARNING file file1.txt not found.
processFile: processing file2.txt.
processFiles: WARNING file file2.txt not found.

Without the try–catch blocks, the program would irretrievably stop right after the first file is not found.

As we can see, an exception is simply a data structure with at least a what method that returns an explanatory string. On throwing the fileNotFound exception, the program makes a non local jump to the place where the exception was caught. processFiles() then prints a simple warning and moves on to the next file.

The last catch block catches any exception other than fileNotFound. It prints the what message and then rethrows the exception because we don’t know what to do with it here. If this exception is not caught elsewhere, the program will terminate.

Pictorially, this is what’s happening :


try → f() → g() → … → throw
 ╭─────────────────────╯
 ↓
catch → …

If something goes wrong (e.g. an exception is thrown), the program unwind the call stack and looks for handlers. If there are none, the programs ends. Otherwise, the handler is executed and control is being transferred where the exception was caught.

There is much more about exception than solely transferring back control into the scope of the handler. For instance, C++ implements RAII (Resource Acquisition Is Initialization) that destroys objects created within the try thus releasing resources automatically. But this requires a destructor for each instantiated object. This means you need to use constructors, that is fstream file("file.txt"); instead of FILE* fd = fopen("file.txt");. If an exception is raised, C++ will happily clean up things for you.

Termination v. Resumption Handlers

Unwinding the stack on exception is termed termination semantics as opposed to resumption semantics (Wikipedia). Resumption semantics do the exact opposite as it allows the program to continue the computation at the exact same spot where the error occurred.

Besides C++ and others mentioned earlier, many languages like Java, C#, Python or Julia implements termination handlers. Languages with resumption handlers include PL/I and several (Lisp–based) functional languages such as Common Lisp, Dylan, and Smalltalk. Some languages such as Go or rust do things differently.

And R has both as we will see in the next part of the series.

  1. Although, in non IEEE 754 abiding context, operations resulting in NaN may throw a (software or hardware) exception. []
  2. As stated by Bruce Eckel & Chuck Allison in their book Thinking in C++, printf() returns the number of characters that were successfully printed but virtually no one checks this value. X []
  3. Roberts Eric S., Implementing Exceptions in C, 1989, DEC Systems Research Center, SRC-RR-40, https://www.cs.tufts.edu/~nr/cs257/archive/eric-roberts/exceptions.pdf. []
  4. As a matter of fact, this is how you can implement exception in C using setjmp/longjmp. Internally, this what R uses to implement exception handling. []

OpenEdition suggests that you cite this post as follows:
Thomas Soubiran (January 21, 2024). Signalling (throwing) and Handling (catching) Custom Conditions (exceptions) in R Ⅰ : Introduction. NUMA. Retrieved July 17, 2024 from https://doi.org/10.58079/vn49


You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Search OpenEdition Search

You will be redirected to OpenEdition Search