Error handling 🔗

On the C++ side 🔗

Every LibraryLink function in C or C++ code has a fixed signature 1

int f (WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res);

The actual result of computations should be returned via the “out-parameter” Res. The value of Res is only considered in the Wolfram Language code if the actual return value of f (of type int) was equal to LIBRARY_NO_ERROR (with LLU use ErrorCode::NoError, see note below).

Tip

LibraryLink uses 8 predefined error codes

enum {
    LIBRARY_NO_ERROR = 0,
    LIBRARY_TYPE_ERROR,
    LIBRARY_RANK_ERROR,
    LIBRARY_DIMENSION_ERROR,
    LIBRARY_NUMERICAL_ERROR,
    LIBRARY_MEMORY_ERROR,
    LIBRARY_FUNCTION_ERROR,
    LIBRARY_VERSION_ERROR
};

LLU redefines those values as constexpr integers in a dedicated namespace LLU::ErrorCode, so for example instead of LIBRARY_FUNCTION_ERROR one can use ErrorCode::FunctionError.

In C++, exceptions are often the preferred way of error handling, so LLU offers a special class of exceptions that can be easily translated to error codes, returned to LibraryLink and then translated to descriptive Failure objects in the Wolfram Language.

Such exceptions are identified in the C++ code by name - a short string. For example, imagine you have a function that reads data from a source. If the source does not exist or is empty, you want to throw exceptions, let’s call them “NoSourceError” and “EmptySourceError”, respectively. First, you must register all your exceptions inside WolframLibrary_initialize function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) {
    try {
        LibraryData::setLibraryData(libData);
        ErrorManager::registerPacletErrors({
            {"NoSourceError", "Requested data source does not exist."},
            {"EmptySourceError", "Requested data source has `1` elements, but required at least `2`."}
        });
    } catch(...) {
        return LLErrorCode::FunctionError;
    }
    return LLErrorCode::NoError;
}

In the code above, the second element of each pair is a textual description of the error which will be visible in the Failure object. This text may contain “slots” denoted as `1`, `2`, etc. that work like TemplateSlot in the Wolfram Language.

Note

Notice that there is no way to assign specific error codes to your custom exceptions, this is handled internally by LLU.

Now, throw exceptions from a function that reads data:

1
2
3
4
5
6
7
8
9
void readData(std::unique_ptr<DataSource> source) {
    if (!source) {
        ErrorManager::throwException("NoSourceError");
    }
    if (source->elemCount() < 3) {
        ErrorManager::throwException("EmptySourceError", source->elemCount(), 3);
    }
    //...
}

Each call to ErrorManager::throwException causes an exception of class LibraryLinkError with predefined name and error code to be thrown. All parameters of throwException after the first one are used to populate consecutive template slots in the error message. The only thing left to do now is to catch the exception. Usually, you catch only in the interface functions (the ones with EXTERN_C DLLEXPORT), extract the error code from exception and return it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
EXTERN_C DLLEXPORT int MyFunction(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) {
    auto err = ErrorCode::NoError;    // no error initially
    try {
        //...
    } catch (const LibraryLinkError& e) {
        err = e.which();    // extract error code from LibraryLinkError
    } catch (...) {
        err = ErrorCode::FunctionError;   // to be safe, handle non-LLU exceptions as well and return generic error code
    }
    return err;
}

On the Wolfram Language side 🔗

The Wolfram Language part of the error-handling functionality of LLU is responsible for converting error codes returned by library functions into nice and informative Failure objects. Whether these objects will be returned or thrown on the WL side is determined by the "Throws" option specified when loading a library function with LLU:

(* This must go first, see the "How to use" -> "Add to your project" section *)
`LLU`InitializePacletLibrary["/path/to/my/library"];

`LLU`PacletFunctionSet[$MyThrowingFunction, "FunctionNameInCppCode", {Integer, String}, Integer] (* by default, "Throws" -> True *)

`LLU`PacletFunctionSet[$MyNonThrowingFunction, "OtherFunctionNameInCppCode", {"DataStore"}, "DataStore", "Throws" -> False]

This code will first load the paclet’s dynamic library located under /path/to/my/library and then it will load exported functions in the library named FunctionNameInCppCode and OtherFunctionNameInCppCode into LibraryFunction expressions and assign them to symbols $MyThrowingFunction and $MyNonThrowingFunction, respectively.

If any call to $MyThrowingFunction fails (returns an error code different than LLU::ErrorCode::NoError) a corresponding Failure expression will be thrown, whereas $MyNonThrowingFunction will return the Failure.

Apart from the C++ code, paclets often have nontrivial amount of Wolfram Language code where errors might also occur. In order to achieve uniform error reporting across C++ and WL, one needs to register errors specific to the WL layer of the paclet:

`LLU`RegisterPacletErrors[<|
   "InvalidInput" -> "Data provided to the function was invalid.",
   "UnexpectedError" -> "Unexpected error occurred with error code: `errCode`."
|>];

RegisterPacletErrors takes an Association of user-defined errors of the form

error_name -> error_message

Such registered errors can later be issued from the Wolfram Language part of the project like this:

status = DoSomething[input];
If[Not @ StatusOK[status],
   `LLU`ThrowPacletFailure["UnexpectedError", "MessageParameters" -> <|"errCode" -> status|>]
]

This code will throw a Failure expression of the following form:

Sample Failure thrown from paclet code 🔗
Failure["UnexpectedError", <|
   "MessageTemplate" -> "The error `errCode` has not been registered.",
   "MessageParameters" -> <|"errCode" -> status|>,
   "ErrorCode" -> 23,   (* assigned internally by LLU, might be different *)
   "Parameters" -> {}
|>]

Important

It is important to remember that all Failures thrown by `LLU`ThrowPacletFailure have a tag (second argument to Throw), so when writing code that is supposed to catch exceptions issued by LLU one must always use 2- or 3-argument version of Catch.

The exact value of the tag used by LLU is `LLU`$ExceptionTagFunction[f], where f is the Failure object to be thrown. In other words, the tag can be any function of the Failure object and developers are encouraged to customize this behavior.

By default, $ExceptionTagFunction is a constant function that returns $ExceptionTagString which is initially set to “LLUExceptionTag”:

$ExceptionTagString = "LLUExceptionTag";
$ExceptionTagFunction := $ExceptionTagString&;

In case you want Failures from your paclet to be thrown with a predefined String tag, say, “MyPacletError”, it is enough to write

`LLU`$ExceptionTagString = "MyPacletError";

If you want to the tag to be different for different kinds of Failures, you may want to do something like this:

`LLU`$ExceptionTagFunction = ("MyPaclet_" <> First[#])&;

This will effectively prefix Failure’s tag with “MyPaclet_”, so for instance for the Failure from listing Sample Failure thrown from paclet code the tag will be “MyPaclet_UnexpectedError”.

There exists an alternative to `LLU`ThrowPacletFailure called LLU`CreatePacletFailure which returns the Failure expression as the result instead of throwing it.

API reference 🔗

namespace LLU::ErrorCode 🔗

Error codes predefined in Library Link.

Variables

constexpr int VersionError = 7 🔗

same as LIBRARY_VERSION_ERROR

constexpr int FunctionError = 6 🔗

same as LIBRARY_FUNCTION_ERROR

constexpr int MemoryError = 5 🔗

same as LIBRARY_MEMORY_ERROR

constexpr int NumericalError = 4 🔗

same as LIBRARY_NUMERICAL_ERROR

constexpr int DimensionsError = 3 🔗

same as LIBRARY_DIMENSIONS_ERROR

constexpr int RankError = 2 🔗

same as LIBRARY_RANK_ERROR

constexpr int TypeError = 1 🔗

same as LIBRARY_TYPE_ERROR

constexpr int NoError = 0 🔗

same as LIBRARY_NO_ERROR


class LLU::LibraryLinkError : public runtime_error 🔗

Class representing an exception in paclet code.

All exceptions that are thrown from paclet code should be of this class. To prevent users from overriding predefined LLU exceptions the constructor of LibraryLinkError class is private. Developers should use ErrorManager::throwException method to throw exceptions.

Public Types

using IdType = int 🔗

A type that holds error id numbers.

Public Functions

LibraryLinkError(const LibraryLinkError &e) noexcept 🔗

Copy-constructor. If there are any messages parameters on the WSLINK, a deep copy is performed.

LibraryLinkError &operator=(const LibraryLinkError &e) noexcept 🔗

Copy-assignment operator.

LibraryLinkError(LibraryLinkError &&e) noexcept 🔗

Move-constructor. Steals messagesParams from e.

LibraryLinkError &operator=(LibraryLinkError &&e) noexcept 🔗

Move-assignment operator.

~LibraryLinkError() override 🔗

The destructor closes the link that was used to send message parameters, if any.

void setDebugInfo(const std::string &dbg) 🔗

Set debug info.

IdType id() const noexcept 🔗

Get the value of error code.

IdType which() const noexcept 🔗

Alias for id() to preserve backwards compatibility.

std::string name() const noexcept 🔗

Get the value of error code.

std::string message() const noexcept 🔗

Get the value of error code.

std::string debug() const noexcept 🔗

Get debug info.

template<typename ...T>
void setMessageParameters(WolframLibraryData libData, T&&... params) 🔗

Store arbitrary number of message parameters in a List expression on a loopback link.

They will travel with the exception until sendParameters is called on the exception.

IdType sendParameters(WolframLibraryData libData, const std::string &WLSymbol = getExceptionDetailsSymbol()) const noexcept 🔗

Send parameters stored in the loopback link to top-level.

They will be assigned as a List to symbol passed in WLSymbol parameter.

Public Static Functions

std::string getExceptionDetailsSymbol() 🔗

Get symbol that will hold details of last thrown exception.

void setExceptionDetailsSymbolContext(std::string newContext) 🔗

Set custom context for the Wolfram Language symbol that will hold the details of last thrown exception.

const std::string &getExceptionDetailsSymbolContext() 🔗

Get current context of the symbol that will hold the details of last thrown exception.


class LLU::ErrorManager 🔗

“Static” class responsible for error registration and throwing

ErrorManager holds a map with all errors that may be thrown from paclet code. These are: LLU errors, framework errors (e.g. MDevices) and paclet-specific errors which should be registered (for example in WolframLibrary_initialize) using registerPacletErrors function. Developers must never throw LibraryLinkErrors directly, instead they should use one of ErrorManager::throwException overloads.

Public Types

using ErrorStringData = std::pair<std::string, std::string> 🔗

A type representing registered error in the form of 2 strings: short error name and longer error description.

Public Functions

ErrorManager() = delete 🔗

Default constructor is deleted since ErrorManager is supposed to be completely static.

Public Static Functions

void registerPacletErrors(const std::vector<ErrorStringData> &errors) 🔗

Function used to register paclet-specific errors.

template<typename ...T>
void throwException(const std::string &errorName, T&&... args) 🔗

Throw exception with given name.

Optionally, pass arbitrary details of the exception occurrence and they will be stored on a loopback link in the exception object. Those details may later be sent via WSTP to top-level and assigned as a List to to the symbol specified in ErrorManager::exceptionDetailsSymbol. To trigger exception details transfer one should call LibraryLinkError::sendParameters on the exception object. However, if ErrorManager::sendParametersImmediately is set to true, this call will be done automatically in throwException.

template<typename ...T>
void throwException(WolframLibraryData libData, const std::string &errorName, T&&... args) 🔗

Throw exception with given name.

Optionally, pass arbitrary details of the exception occurrence and they will be stored on a loopback link in the exception object. Those details may later be sent via WSTP to top-level and assigned as a List to to the symbol specified in ErrorManager::exceptionDetailsSymbol. To trigger exception details transfer one should call LibraryLinkError::sendParameters on the exception object. However, if ErrorManager::sendParametersImmediately is set to true, this call will be done automatically in throwException.

template<class Error, typename ...Args>
void throwCustomException(const std::string &errorName, Args&&... args) 🔗

Throw exception of given class that carries the error with given name.

This is useful if you want to throw custom exception classes from your paclet and still see the nice Failure objects in top-level.

template<typename ...T>
void throwExceptionWithDebugInfo(const std::string &errorName, const std::string &debugInfo, T&&... args) 🔗

Throw exception with given name and additional information that might be helpful in debugging.

Optionally, pass arbitrary details of the exception occurrence and they will be stored on a loopback link in the exception object. Those details may later be sent via WSTP to top-level and assigned as a List to to the symbol specified in ErrorManager::exceptionDetailsSymbol. To trigger exception details transfer one should call LibraryLinkError::sendParameters on the exception object. However, if ErrorManager::sendParametersImmediately is set to true, this call will be done automatically in throwException. The debugInfo is a string stored inside the LibraryLinkError object. It is never transferred to top-level but might be for example logged to a file in a “catch” block in C++ code.

template<typename ...T>
void throwExceptionWithDebugInfo(WolframLibraryData libData, const std::string &errorName, const std::string &debugInfo, T&&... args) 🔗

Throw exception with given name and additional information that might be helpful in debugging.

Optionally, pass arbitrary details of the exception occurrence and they will be stored on a loopback link in the exception object. Those details may later be sent via WSTP to top-level and assigned as a List to to the symbol specified in ErrorManager::exceptionDetailsSymbol. To trigger exception details transfer one should call LibraryLinkError::sendParameters on the exception object. However, if ErrorManager::sendParametersImmediately is set to true, this call will be done automatically in throwException. The debugInfo is a string stored inside the LibraryLinkError object. It is never transferred to top-level but might be for example logged to a file in a “catch” block in C++ code.

void setSendParametersImmediately(bool newValue) 🔗

Sets new value for the sendParametersImmediately flag.

Pass false to make sure that exception do not send their parameters to top-level when they are thrown. This is essential in multithreaded applications since the WL symbol that parameters are assigned to may be treated as a global shared resource. It is recommended to use this method in WolframLibrary_initialize.

bool getSendParametersImmediately() 🔗

Get the current value of sendParametersImmediately flag.

Function used to send all registered errors to top-level Mathematica code.

Sending registered errors allows for nice and meaningful Failure objects to be generated when paclet function fails in top level, instead of usual LibraryFunctionError expressions.

Footnotes

1

One more possible signature is int f(WolframLibraryData, WSLINK). For such functions error handling is done in the same way.