Error Handling in Lua
Robust error handling is crucial for building reliable applications.
Lua
provides several mechanisms for handling errors: error(),
assert(), pcall(), and xpcall(). In this
lesson, you'll learn how to handle errors gracefully and build resilient code.
Let's
explore error handling in Lua!
Raising Errors
Using error()
function divide(a, b)
if b == 0 then
error("Division by zero")
end
return a / b
end
-- This will raise an error
-- divide(10, 0) -- Error: Division by zero
Error with Level
function validateAge(age)
if type(age) ~= "number" then
error("Age must be a number", 2) -- Level 2: caller's location
end
if age < 0 or age > 150 then
error("Age must be between 0 and 150", 2)
end
return true
end
1(default): Error at current function2: Error at caller's location0: No location information
Using assert()
assert() raises an error if a condition is false:
function sqrt(x)
assert(x >= 0, "Cannot take square root of negative number")
return math.sqrt(x)
end
print(sqrt(16)) -- 4
-- print(sqrt(-4)) -- Error: Cannot take square root of negative number
Assert with Function Calls
-- Assert that a file opened successfully
local file = assert(io.open("data.txt", "r"), "Failed to open file")
-- Assert function return values
local function getConfig()
return nil, "Configuration not found"
end
local config = assert(getConfig()) -- Error: Configuration not found
Click Run to execute your code
Protected Calls with pcall()
pcall() (protected call) catches errors without stopping execution:
function riskyFunction()
error("Something went wrong!")
end
local success, result = pcall(riskyFunction)
if success then
print("Success:", result)
else
print("Error:", result) -- Error: Something went wrong!
end
pcall() with Arguments
function divide(a, b)
if b == 0 then
error("Division by zero")
end
return a / b
end
local success, result = pcall(divide, 10, 2)
print(success, result) -- true 5
local success, error = pcall(divide, 10, 0)
print(success, error) -- false Division by zero
Practical pcall() Usage
local function safeRequire(module)
local success, result = pcall(require, module)
if success then
return result
else
print("Warning: Failed to load module " .. module)
return nil
end
end
local json = safeRequire("json")
if json then
-- Use json module
end
Click Run to execute your code
xpcall() with Error Handler
xpcall() allows you to specify a custom error handler:
local function errorHandler(err)
return "Error occurred: " .. tostring(err) .. "\n" .. debug.traceback()
end
local function riskyFunction()
local x = nil
return x.field -- Error: attempt to index nil value
end
local success, result = xpcall(riskyFunction, errorHandler)
if not success then
print(result) -- Prints error with stack trace
end
Custom Error Handler
local function detailedErrorHandler(err)
local info = {
error = tostring(err),
timestamp = os.date("%Y-%m-%d %H:%M:%S"),
traceback = debug.traceback()
}
return info
end
local function buggyFunction()
error("Oops!")
end
local success, errorInfo = xpcall(buggyFunction, detailedErrorHandler)
if not success then
print("Error:", errorInfo.error)
print("Time:", errorInfo.timestamp)
print("Trace:", errorInfo.traceback)
end
Click Run to execute your code
Error Handling Patterns
Return nil, error Pattern
local function readFile(filename)
local file, err = io.open(filename, "r")
if not file then
return nil, "Failed to open file: " .. err
end
local content = file:read("*all")
file:close()
return content
end
local content, err = readFile("data.txt")
if not content then
print("Error:", err)
else
print("Content:", content)
end
Try-Catch Pattern
local function try(func, catch)
local success, result = pcall(func)
if not success then
if catch then
catch(result)
end
return nil
end
return result
end
-- Usage
try(function()
error("Something went wrong")
end, function(err)
print("Caught error:", err)
end)
Retry Pattern
local function retry(func, maxAttempts, delay)
local attempts = 0
while attempts < maxAttempts do
attempts = attempts + 1
local success, result = pcall(func)
if success then
return result
end
if attempts < maxAttempts then
print("Attempt " .. attempts .. " failed, retrying...")
-- In real code, you'd sleep here
end
end
error("Failed after " .. maxAttempts .. " attempts")
end
-- Usage
local result = retry(function()
-- Potentially failing operation
if math.random() > 0.7 then
return "Success!"
else
error("Random failure")
end
end, 3)
Click Run to execute your code
Validation and Error Messages
Input Validation
local function createUser(name, email, age)
-- Validate name
assert(type(name) == "string", "Name must be a string")
assert(#name > 0, "Name cannot be empty")
-- Validate email
assert(type(email) == "string", "Email must be a string")
assert(email:match("^[%w.]+@[%w.]+%.%w+$"), "Invalid email format")
-- Validate age
assert(type(age) == "number", "Age must be a number")
assert(age >= 0 and age <= 150, "Age must be between 0 and 150")
return {
name = name,
email = email,
age = age
}
end
local success, result = pcall(createUser, "Alice", "[email protected]", 25)
if success then
print("User created:", result.name)
else
print("Validation error:", result)
end
Custom Error Types
local ValidationError = {}
ValidationError.__index = ValidationError
function ValidationError:new(field, message)
local self = setmetatable({}, ValidationError)
self.type = "ValidationError"
self.field = field
self.message = message
return self
end
function ValidationError:__tostring()
return string.format("ValidationError [%s]: %s", self.field, self.message)
end
local function validateUser(user)
if not user.name or #user.name == 0 then
error(ValidationError:new("name", "Name is required"))
end
if not user.email or not user.email:match("@") then
error(ValidationError:new("email", "Invalid email"))
end
return true
end
local success, err = pcall(validateUser, {name = "", email = "invalid"})
if not success then
if type(err) == "table" and err.type == "ValidationError" then
print("Validation failed:", err.field, err.message)
else
print("Unknown error:", err)
end
end
Error Handling Best Practices
- Use pcall() for risky operations: File I/O, network calls, external modules
- Provide meaningful error messages: Help users understand what went wrong
- Validate early: Check inputs at function entry
- Use assert() for preconditions: Document assumptions
- Return nil, error for expected failures: Use error() for unexpected failures
- Log errors appropriately: Include context and stack traces
- Clean up resources: Use pcall() with cleanup in finally pattern
Practice Exercise
Try these error handling challenges:
Click Run to execute your code
Summary
In this lesson, you learned:
- Raising errors with
error()andassert() - Protected calls with
pcall() - Custom error handlers with
xpcall() - Common error handling patterns (try-catch, retry, nil-error)
- Input validation and custom error types
- Error handling best practices
What's Next?
You've mastered error handling! Next, we'll explore debugging techniques in Lua. You'll learn how to use the debug library, print debugging, logging, and tools for finding and fixing bugs. Let's continue! 🚀
Enjoying these tutorials?