Error Handling
The MAXON API uses a custom error reporting system. In case of an error, a function can return an error object. The return value and the error object are stored in a maxon::Result object. Within a function scope it is also necessary to handle errors returned by sub-functions.
A function that returns a maxon::Result object can either return just the plain return value or a complex error object. This error object can contain detailed information on the error that occurred. For more details on the maxon::Result class see Error Result . For a list of default error types see Error Types .
// This example shows a function that returns a number or an error.//---------------------------------------------------------------------------------------- // Creates a random number by rolling a dice. // @return Random number or UnknownError if the dice could not be rolled. //---------------------------------------------------------------------------------------- static maxon::Result<maxon::Int> GetRandomNumber() { maxon::Int number = 0;
Functions that do not return any value can return Result<void> :
// This example shows a function that does not return a value // but can return an error in case of an invalid argument or just maxon::OK. // Note: Real code should use a reference and not a pointer.//---------------------------------------------------------------------------------------- // Increments the given maxon::Int value. // Returns an NullptrError if an invalid argument is given. // @param[in,out] number Pointer to an maxon::Int value. // @return OK on success. //---------------------------------------------------------------------------------------- static maxon::Result<void> IncrementValue( maxon::Int * const number) { if (number == nullptr ) return maxon::NullptrError( MAXON_SOURCE_LOCATION ); *number = *number + 1; return maxon::OK ; }
It is possible to create an error object before it is needed to increase the performance in speed critical tasks:
// preallocate the illegal-argument-error g_sampleArgumentError static MAXON_ERROR_PREALLOCATE (g_sampleArgumentError, []() { return maxon::IllegalArgumentError( MAXON_SOURCE_LOCATION ); }); static maxon::Result<maxon::Float32> SampleGradient( maxon::Float32 pos) { // check range // if out of bounds return the pre-allocated error if (pos < 0.0 || pos > 1.0) return g_sampleArgumentError; return Sample(pos); }
Within a given error scope one must define how errors returned by sub-functions are handled.
Best practice to handle errors is typically to declare an error scope and to simply return the error returned by a sub-function:
In such an error scope one can simply return from the function if an error occurred in a sub-function:
// return if an error is returned const maxon::Int randomNumber = GetRandomNumber() iferr_return ; const maxon :: Float randomFloat = ( maxon :: Float )randomNumber / numberOfFaces; return randomFloat; }
If a simple error scope is declared with iferr_scope, the attribute iferr_return will return from the function returning the detected error. In contrast, iferr_scope_handler allows to define a function that is called before iferr_return will return.
Compare also Finally .
// This function handles the error returned by GetRandomNumber() // by printing the error to the console and returning a default value.//---------------------------------------------------------------------------------------- // Returns a random float value. // @return A random maxon::Float value. //---------------------------------------------------------------------------------------- static maxon::Float GetRandomFloat() { iferr_scope_handler { // is called by iferr_return before it returns from the function DiagnosticOutput ( "GetRandomFloat() error: @" , err);
// defines the return value return 0.0; };
// return if an error is returned const maxon::Int randomNumber = GetRandomNumber() iferr_return ; const maxon :: Float randomFloat = ( maxon :: Float )randomNumber / numberOfFaces; return randomFloat; }
It is also possible to simply check if a called function returned an error or not.
err
that can be returned.
//---------------------------------------------------------------------------------------- // Returns a random float value. // @return A random maxon::Float value. //---------------------------------------------------------------------------------------- static maxon::Result<maxon::Float> GetRandomFloat() { maxon::Int randomNumber = 0;
// return if an error is returned iferr (randomNumber = GetRandomNumber()) { // "err" is defined in iferr return err; } const maxon::Float randomFloat = ( maxon::Float )randomNumber / numberOfFaces; return randomFloat; }
If a new error is created within a function it is typically just returned. If one wants iferr_scope_handler to be called before one must use iferr_throw:
// defines the return value return 0.0; };
// check "scale" argument // use iferr_throw to make sure iferr_scope_handler is called if (scale == 0.0) iferr_throw (maxon::IllegalArgumentError( MAXON_SOURCE_LOCATION ));
// return if an error is returned const maxon::Int randomNumber = GetRandomNumber() iferr_return ; const maxon :: Float randomFloat = ( maxon :: Float )randomNumber / scale; return randomFloat; }
In some cases an error can be ignored. Such cases must be marked and explained explicitly:
// create array and ensure capacity maxon::BaseArray<maxon::Int> numbers; numbers. EnsureCapacity (count) iferr_return ;
// fill array for ( maxon::Int i = 0; i < count; ++i) { numbers. Append (i) iferr_cannot_fail ( "Array capacity ensured" ); }
// just try to increase capacity // use the second parameter "debug" to trigger a debug stop in case of an error numbers. EnsureCapacity (newCount) iferr_ignore ( "Capacity size not important." , debug);
Error handling can be limited to a sub-scope within a function by using a lambda:
// This example limits the error handling to an lambda. // All errors returned from that lambda are handled in one point. static maxon::Float GetRandomFloat( maxon::Float scale) { // define lambda with internal error handling auto GetFloat = [scale]() -> maxon::Result<maxon::Float> { iferr_scope ; if (scale == 0.0) return maxon::IllegalArgumentError( MAXON_SOURCE_LOCATION );// return if an error is returned const maxon::Int randomNumber = GetRandomNumber() iferr_return ; const maxon :: Float randomFloat = ( maxon :: Float )randomNumber / scale; return randomFloat; }; maxon :: Float randomFloat = 0.0;
// call lambda and handle error iferr (randomFloat = GetFloat()) DiagnosticOutput ("Error: @", err); return randomFloat; }
The attribute iferr_return allows to return from a function when an error was detected. This could lead to issues with resources that have to be managed and freed within the function scope. For example a certain resource must be freed when the function is ending. Allocated objects and memory are best handled using 参考 .
For more complex situations one can use either iferr_scope_handler or finally:
// print error DiagnosticOutput ( "Error: @" , err);
// return error return err; }; PrepareInternalData() iferr_return ; HandleInternalData() iferr_return ;
// check for success // use iferr_throw to make sure iferr_scope_handler is called if (GetInternalSuccess() == false) iferr_throw ( maxon ::UnexpectedError( MAXON_SOURCE_LOCATION )); StoreInternalData() iferr_return ; FreeInternalData(); return maxon :: OK ; }
// This example shows a function that has to prepare and free internal data. // FreeInternalData() must always be called when the function is left. // The "finally" macro is used to ensure that FreeInternalData() is called // when the function is left, independent of how the function is left. static maxon::Result<void> PerformInternalTask() { iferr_scope ; finally { // make sure internal data is always freed FreeInternalData(); }; PrepareInternalData() iferr_return ; HandleInternalData() iferr_return ;
// check for success if (GetInternalSuccess() == false) return maxon ::UnexpectedError( MAXON_SOURCE_LOCATION ); StoreInternalData() iferr_return ; return maxon :: OK ; }