ErrorSentinel - Python-Style Exception Handling for Roblox
ErrorSentinel allows the use of ‘exception handling’ to LuaU, offering Python-inspired try-catch-finally blocks, hierarchical exception types, and error management. This aims to stop the use of pcalls, in a more intriguing manner.
I highly suggest you take the time to read this!
Overview
Purpose: Provides the art of exception handling with very familiar Python-style syntax for LuaU!
Target Audience: Developers who may seek robust error management beyond basic pcall patterns
Requirements: Intermediate skill-level in LuaU, and a small amount of familiarity in Python (not much!)
Key Features
- Exception System - Organized exception types with inheritance chains (i.e., ValueError, TypeError, RuntimeError, etc.)
- Try-Catch-Finally Blocks - If you are familiar with the Promise pattern, this provides familiar control with ‘except’, ‘else’ and ‘finally’ handlers!
- Type Safety - Fully LuaU type annotated!
- Safe Function Wrapping - Very useful function that transforms any function into a non-throwing variant with default returns!
- Context Managers - ‘with’ function that provides resource management with automatic cleanup
Important Notice
Sentinel doesn’t actually know when to exactly cause an exception. It doesn’t know when it occurs; whether it is from a DataStoreService; HTTPService etc. Exceptions that are NOT RuntimeErrors are purely for controlled code flow.
Sentinel does know a RuntimeError; anytime the LuaU scheduler throws an error; Sentinel catches that error if there is an Exception defined for it.
Installation
- See down below, Downloads
Quick Start
lua
-- How to use quickly
local Error = require(game:GetService("ReplicatedStorage").ErrorSentinel)
local Exceptions = Error.Exceptions
-- Basic try-catch pattern
-- This demonstrates the creation of an Instance that catches any error (Type or Runtime)
local function createInstance(className: string)
return Error.try(true,
function()
if type(className) ~= "string" then
-- Raise an error
Error.raise(Exceptions.TypeError.new(`Expected a string, got '{className}'`))
end
-- If className isn't a valid Instance, this will also error; and is considered a Runtime error
-- since we don't have direct control of it
return Instance.new(className)
end,
-- Let's check for a RuntimeError (Could be triggered if className isnt a valid Instance)
Error.except(Exceptions.RuntimeError, function(exc)
warn(`RuntimeError triggered: '{exc.message}'`)
end),
-- Let's also check for the TypeError (Which is manually raised)
Error.except(Exceptions.TypeError, function(exc)
warn(`TypeError triggered: '{exc.message}'`)
end)
)
end
local part = createInstance("Part") -- This creates a part.
-- If you were to not pass a string, or it is a string but not a valid Instance, it will get caught
More Usage Examples
Basic Usage
Simple try-catch with specific exception types
lua
local function divide(a, b)
return ErrorSentinel.try(
function()
if type(a) ~= "number" or type(b) ~= "number" then
ErrorSentinel.raise(Exceptions.TypeError.new("Both arguments must be numbers"))
end
if b == 0 then
ErrorSentinel.raise(Exceptions.ZeroDivisionError.new("Cannot divide by zero"))
end
return a / b
end,
ErrorSentinel.except(Exceptions.ZeroDivisionError, function(e)
warn("Division error:", e.message)
return 0
end),
ErrorSentinel.except(Exceptions.TypeError, function(e)
warn("Type error:", e.message)
return nil
end)
)
end
local result = divide(10, 2) -- Returns 5
local result2 = divide(10, 0) -- Returns 0, warns about division
Try-catch with else and finally blocks
lua
local success = ErrorSentinel.try(
function()
-- Risky operation
return performDatabaseOperation()
end,
ErrorSentinel.except(Exceptions.RuntimeError, function(e)
print("Database operation failed:", e.message)
end),
ErrorSentinel.else_(function()
print("Database operation completed successfully")
end),
ErrorSentinel.finally(function()
print("Cleaning up database connection")
cleanup()
end)
)
Advanced Usage
Safe function wrapping for external APIs
lua
local HttpService = game:GetService("HttpService")
local safeHttpGet = ErrorSentinel.safe(
function(url)
local response = HttpService:GetAsync(url)
return HttpService:JSONDecode(response)
end,
-- This is where defaults can be specified if errors
{error = "Request failed"}, -- Default return value
-- This is just an exception function that warns
function(exception)
warn("HTTP request error:", exception.message)
end
)
-- Never throws, always returns a table
local data = safeHttpGet("https://api.example.com/data")
API Reference
Core Functions
ErrorSentinel.try(returnBoth?, tryBlock, …handlers)
- Parameters:
returnBoth: boolean?
- If true, returnssuccess, result
tuple; if false/nil, returns single resulttryBlock: () -> any
- Function to execute...handlers: ExceptionHandler
- Exception handlers created with except/else_/finally
- Returns:
any
or(boolean, any)
depending on returnBoth parameter - Description: Executes tryBlock with structured exception handling
ErrorSentinel.raise(exception)
- Parameters:
exception: Exception | string
- Exception to raise (strings are wrapped in base Exception) - Returns:
never
- Always throws - Description: Raises an exception, similar to Python’s raise statement
ErrorSentinel.except(exceptionTypes, handler)
- Parameters:
exceptionTypes: ExceptionConstructor | {ExceptionConstructor}
- Exception type(s) to catchhandler: (exception) -> any
- Function to handle the exception
- Returns:
ExceptionHandler
- Handler for use with try() - Description: Creates exception handler for specific exception types
ErrorSentinel.safe(func, defaultReturn, exceptionHandler?)
- Parameters:
func: (...any) -> R
- Function to wrapdefaultReturn: R
- Value to return if exception occursexceptionHandler: ((exception) -> ())?
- Optional exception processor
- Returns:
(...any) -> R
- Wrapped function that never throws - Description: Creates safe wrapper that catches all exceptions and returns default value
ErrorSentinel.isinstance(obj, classTypes)
- Parameters:
obj: any
- Object to checkclassTypes: ExceptionConstructor | {ExceptionConstructor}
- Type(s) to check against
- Returns:
boolean
- True if obj is instance of any classType - Description: Checks if object is instance of given exception type(s), including inheritance
Exception Types
lua
-- Base hierarchy
ErrorSentinel.Exceptions.BaseException
ErrorSentinel.Exceptions.Exception
-- Standard exceptions
ErrorSentinel.Exceptions.ValueError
ErrorSentinel.Exceptions.TypeError
ErrorSentinel.Exceptions.IndexError
ErrorSentinel.Exceptions.KeyError
ErrorSentinel.Exceptions.AttributeError
ErrorSentinel.Exceptions.RuntimeError
ErrorSentinel.Exceptions.NotImplementedError
ErrorSentinel.Exceptions.ZeroDivisionError
ErrorSentinel.Exceptions.FileNotFoundError
ErrorSentinel.Exceptions.PermissionError
-- Roblox-specific exceptions
ErrorSentinel.Exceptions.RobloxError
ErrorSentinel.Exceptions.RemoteEventError
ErrorSentinel.Exceptions.DataStoreError
ErrorSentinel.Exceptions.NetworkError
Performance Notes
An important thing to note is, while this does provide extensive error handling, it is also significantly more overhead than simple pcalls or error calls.
The difference is probably negligible, just important to know.
Download
Get on Roblox Marketplace: ErrorSentinel | Python-like Error Handling
Feedback
Any feedback is genuinely appreciated, I highly suggest you give this a chance and give your opinions.