Error Handling

Errors can happen when code tries to do something invalid, such as reading missing data, calling something incorrectly, or when an explicit exceptions is thrown. Lunaris provides a structured error handling with try, catch, finally, throw, exception and attempt syntax for error handling.

Try & Catch

Use try when code might fail, and catch when you want to handle the failure.

try {
    throw "Something went wrong"
} catch (err) {
    print(err.Message) // Something went wrong
}

Throw

Use throw to raise an exception manually

function SetHealth(value) {
    if (value < 0) {
        throw "Health cannot be negative"
    }
}

If a non-exception value such as a string or number is thrown, Lunaris automatically wraps it into an exception object.

try {
    throw 42
}
catch (err) {
    print(err.Message) // 42
    print(err.Value)   // 42
}

Finally

finally always runs, whether the error happened or not. This is useful for cleanup

function TryProcessData(data) {
    try {
        if (data is nil) throw new ArgumentException("data cannot be nil")
        print("Opening data")
    } catch {
        print("Data could not be opened")
    }
    finally {
        print("Closing data")
    }
}

TryProcessData("Hello World") // Opening data, Closing data
TryProcessData(nil) // Data could not be opened, Closing data

finally also runs if code returns a value

function Test() {
    try {
        return 10
    }
    finally {
        print("Cleanup runs first")
    }
}
print(Test) // Cleanup runs first, 10

Exception

Exceptions are of the table-type exception. You can define your own exception types which can be used with the catch and throw keywords.

exception NumberCannotBeNegativeException {

    Value = 0

    NumberCannotBeNegativeException(number) {
        this.Message = "Number cannot be negative"
        this.Value = number
    }
}

local number = -5
try {
    if (number < 0) throw new NumberCannotBeNegativeException(number)
} catch(err) {
    print(err.Message) // Number cannot be negative
    print(err.Value) // -5
}

Typed catch

With typing, a catch block can specify the type of exception it handles.

exception NumberCannotBeNegativeException {
    Value = 0
    NumberCannotBeNegativeException(number) {
        this.Message = "Number cannot be negative"
        this.Value = number
    }
}

local number = -5
try {
    if (number < 0) throw new NumberCannotBeNegativeException(number)
} catch(err: NumberCannotBeNegativeException) {
    print(err.Message) // Number cannot be negative
    print(err.Value) // -5
}
// If the exception isn't a NumberCannotBeNegativeException, then the exception is still thrown as its unhandled

Attempt

The attempt keywords allows for a shorter way to handle with try/catch. It evaluates an expression and returns nil if an exception occurs. This is useful when an exception is safe to ignore.

// GetPlayerName throws a RuntimeException
local name = attempt GetPlayerName() // nil

Without the attempt keyword, the same can be done with an try/catch

local name = nil
try {
    name = GetPlayerName()
} catch {
    // Ignored
}

Attempt … Else

Use attempt <expression> else <fallback> when you want a fallback value only if an exception happened.

local name = attempt GetPlayerName() else "Unknown"

Note:

  • If GetPlayerName() succeeds, the return value of the function is used.
  • If it throws any exception, "Unknown" is returned
  • If it succeeds and returns nil, the result is still nil.

So else in combination with attempt is only used for exceptions, not for nil results. Use the ?? operator for nil-coalescing.

Examples

exception PlayerNotFoundException {
    PlayerNotFoundException(message) {
        this.Message = message
    }
}

function GetPlayerName(id) {
    if (id != 1) {
        throw new PlayerNotFoundException("Player was not found")
    }

    return "Lithrun"
}

local name = attempt GetPlayerName(2) else "Unknown"
print(name) // Unknown

try {
    print(GetPlayerName(2))
}
catch (err: PlayerNotFoundException) {
    print("Handled error: " + err.Message)
}
finally {
    print("Finished player lookup")
}

Categories:

Updated: