Open Search
    Error Handling

    About

    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.

    Return Errors

    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;
    if (RollDice(number) == false)
    return maxon::UnknownError(MAXON_SOURCE_LOCATION, "Could not roll the dice."_s);
    return number;
    }
    Definition: resultbase.h:766
    Int64 Int
    signed 32/64 bit int, size depends on the platform
    Definition: apibase.h:202
    #define MAXON_SOURCE_LOCATION
    Definition: memoryallocationbase.h:67

    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;
    }
    return OK
    Definition: apibase.h:2735

    It is possible to create an error object before it is needed to increase the performance in speed critical tasks:

    • MAXON_ERROR_PREALLOCATE: Macro used to preallocate an error object.
    // This example uses a preallocated error object to return an error efficiently.
    // preallocate the illegal-argument-error g_sampleArgumentError
    static MAXON_ERROR_PREALLOCATE(g_sampleArgumentError, []() { return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION); });
    {
    // check range
    // if out of bounds return the pre-allocated error
    if (pos < 0.0 || pos > 1.0)
    return g_sampleArgumentError;
    return Sample(pos);
    }
    void Py_ssize_t * pos
    Definition: dictobject.h:50
    float Float32
    32 bit floating point value (float)
    Definition: apibase.h:196
    #define MAXON_ERROR_PREALLOCATE(errorName, init)
    Definition: errorbase.h:434

    Error Scope

    Within a given error scope one must define how errors returned by sub-functions are handled.

    Note
    An error returned by a function must be handled explicitly. Ignoring the returned error object will result in a compile error on certain systems.

    Best practice to handle errors is typically to declare an error scope and to simply return the error returned by a sub-function:

    • iferr_scope: Declares an error scope.
    • iferr_scope_handler: Executes the defined code when called with iferr_return.
    • iferr_scope_result: Can be used in a finally block to check the error state.

    In such an error scope one can simply return from the function if an error occurred in a sub-function:

    • iferr_return: If an error occurred, this error is returned.
    //----------------------------------------------------------------------------------------
    // Returns a random float value.
    // @return A random maxon::Float value.
    //----------------------------------------------------------------------------------------
    static maxon::Result<maxon::Float> GetRandomFloat()
    {
    // declare error scope
    // return if an error is returned
    const maxon::Int randomNumber = GetRandomNumber() iferr_return;
    const maxon::Float randomFloat = (maxon::Float)randomNumber / numberOfFaces;
    return randomFloat;
    }
    maxon::Float Float
    Definition: ge_sys_math.h:62
    The maxon namespace contains all declarations of the MAXON API.
    Definition: autoweight.h:14
    #define iferr_scope
    Definition: resultbase.h:1389
    #define iferr_return
    Definition: resultbase.h:1524

    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.

    Note
    When called with iferr_return, the return value of iferr_scope_handler is used as the return value of the function.

    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()
    {
    {
    // 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;
    }
    Float64 Float
    Definition: apibase.h:211
    #define DiagnosticOutput(formatString,...)
    Definition: debugdiagnostics.h:170
    #define iferr_scope_handler
    Definition: resultbase.h:1407

    It is also possible to simply check if a called function returned an error or not.

    • iferr: Checks if the given function returned an Error. Defines maxon::ErrorPtr err that can be returned.
    • ifnoerr: Checks if the given function returned no error. Defines maxon::NoErrorPtr.
    // This function checks if GetRandomNumber() returns an error.
    // If so, this error is returned by the function itself.
    //----------------------------------------------------------------------------------------
    // 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;
    }
    #define iferr(...)
    Definition: errorbase.h:388

    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:

    • iferr_throw: Throws an error that is caught in iferr_scope_handler.
    // This function prints all errors that occur before the function is left.
    // iferr_throw must be used to ensure that this is also true for newly created errors.
    static maxon::Float GetRandomFloat(maxon::Float scale)
    {
    {
    // is called by iferr_return or iferr_throw before it returns from the function
    DiagnosticOutput("Error: @", err);
    // 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;
    }
    #define iferr_throw(ERR)
    Definition: resultbase.h:1589

    In some cases an error can be ignored. Such cases must be marked and explained explicitly:

    Note
    Use these attributes only if the error can be ignored or there is no way of handling the error. Always describe the reason why this macro was used.
    // This example shows how an error can be ignored explicitly.
    // create array and ensure capacity
    // 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);
    Py_ssize_t i
    Definition: abstract.h:645
    Py_ssize_t count
    Definition: abstract.h:640
    Definition: basearray.h:415
    ResultMem EnsureCapacity(Int requestedCapacity, COLLECTION_RESIZE_FLAGS resizeFlags=COLLECTION_RESIZE_FLAGS::ON_GROW_RESERVE_CAPACITY)
    Definition: basearray.h:1329
    MAXON_ATTRIBUTE_FORCE_INLINE ResultRef< T > Append(ARG &&x)
    Appends a new element at the end of the array and constructs it using the forwarded value.
    Definition: basearray.h:628
    #define iferr_ignore(...)
    Definition: resultbase.h:1489
    #define iferr_cannot_fail(str)
    Definition: resultbase.h:1468

    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>
    {
    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;
    }

    Error Handling and Resource Management

    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 References.

    For more complex situations one can use either iferr_scope_handler or finally:

    • The resource clean-up can be handled with iferr_scope_handler. If this is the case any custom error returned from within the function must use iferr_throw to make sure that iferr_scope_handler is called before the function is left.
    • The resource clean-up can be handled with finally.
    // This example shows a function that has to prepare and free internal data.
    // FreeInternalData() must always be called when the function is left.
    // iferr_scope_handler is used to ensure that FreeInternalData() is called
    // in the case an error is returned.
    static maxon::Result<void> PerformInternalTask()
    {
    {
    // make sure internal data is freed when an error occurred
    FreeInternalData();
    // 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)
    StoreInternalData() iferr_return;
    FreeInternalData();
    return maxon::OK;
    }
    OK
    User has selected a font.
    Definition: customgui_fontchooser.h:0
    // 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()
    {
    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;
    }

    Further Reading