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 stillnil.
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")
}