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:
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
-
constexpr int
-
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.
-
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
>
voidsetMessageParameters
(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.
-
using
-
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
>
voidthrowException
(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
>
voidthrowException
(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
>
voidthrowCustomException
(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
>
voidthrowExceptionWithDebugInfo
(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
>
voidthrowExceptionWithDebugInfo
(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.
-
void
sendRegisteredErrorsViaWSTP
(WSLINK mlp) 🔗 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.
-
using
Footnotes
- 1
One more possible signature is
int f(WolframLibraryData, WSLINK)
. For such functions error handling is done in the same way.