feat: add error handling part #3
240
README.md
240
README.md
@@ -406,19 +406,239 @@ print(coroutine.resume(routine, 7, 8, 9)) -- 1+2+3 = '6'
|
||||
|
||||
### Error handling
|
||||
|
||||
Lua allows low-level error handling with the `error` function and high-level error handling with the `assert` function. The `error` function raises an error and handles it with the `pcall` or `xpcall` function. The `assert` function checks a condition and raises an error if the condition is not met.
|
||||
Lua provides several core functions for error handling: `assert`, `error`, `pcall` (protected call), and `xpcall` (extended protected call), each offering different levels of control.
|
||||
The examples below will explain and illustrate how each method is intended to be used.
|
||||
|
||||
````lua
|
||||
#### assert
|
||||
|
||||
`assert` is used to check a condition. If the result is `false` or `nil`, it triggers an error with a custom message.
|
||||
It's especially useful when validating user input or enforcing preconditions.
|
||||
|
||||
```lua
|
||||
local function isMajorUser(age)
|
||||
assert(age >= 18, "You are not allowed to buy this item, you need to be 18 years old")
|
||||
end
|
||||
|
||||
isMajorUser(15)
|
||||
-- Output : You are not allowed to buy this item, you need to be 18 years old !
|
||||
```
|
||||
|
||||
#### error(message [, level])
|
||||
|
||||
The `error` function allows you to manually raise an error, similar to `throw` in other languages. It immediately stops the current execution flow.
|
||||
You can provide a custom error message, as well as a `level` parameter to control where the traceback starts in the call stack.
|
||||
|
||||
Possible values for `level`:
|
||||
|
||||
- **0**: No call stack information is shown.
|
||||
|
||||
```lua
|
||||
error("Simple error without trace", 0)
|
||||
-- Result: Simple error without trace
|
||||
```
|
||||
|
||||
- **1** *(default)*: The error appears at the exact place where `error` is called.
|
||||
|
||||
```lua
|
||||
local function triggerError()
|
||||
error("An error has occurred", 1)
|
||||
end
|
||||
|
||||
triggerError()
|
||||
|
||||
-- Result
|
||||
--[[
|
||||
lua: script.lua:2: An error has occurred
|
||||
stack traceback:
|
||||
script.lua:2: in function 'triggerError'
|
||||
script.lua:5: in main chunk
|
||||
]]
|
||||
```
|
||||
|
||||
- **2**: The error appears at the place where the function **calling `error`** was itself called.
|
||||
|
||||
```lua
|
||||
local function triggerError()
|
||||
error("Error reported to caller", 2)
|
||||
end
|
||||
|
||||
local function wrapper()
|
||||
triggerError()
|
||||
end
|
||||
|
||||
wrapper()
|
||||
|
||||
-- Result
|
||||
--[[
|
||||
lua: script.lua:7: Error reported to caller
|
||||
stack traceback:
|
||||
script.lua:7: in function 'wrapper'
|
||||
script.lua:10: in main chunk
|
||||
]]
|
||||
```
|
||||
|
||||
#### pcall (protected call)
|
||||
|
||||
As its name suggests, `pcall` allows you to execute a function in **protected mode**, catching any potential error that might occur.
|
||||
It acts similarly to a `try-catch` block found in other programming languages.
|
||||
|
||||
The result of calling `pcall` returns two values, typically stored in two variables like `success` and `result`, though you can name them as you wish.
|
||||
|
||||
- If the call succeeds:
|
||||
- `success` will be `true`,
|
||||
- `result` will contain the function’s returned value(s).
|
||||
|
||||
- If an error occurs:
|
||||
- `success` will be `false`,
|
||||
- `result` will contain the error message.
|
||||
|
||||
If the function does not return anything, `result` will be `nil`.
|
||||
|
||||
```lua
|
||||
local function divideANumberByAnOther(a, b)
|
||||
return a / b -- it would be better to validate parameters, but this is for example purposes
|
||||
end
|
||||
|
||||
-------------------------------
|
||||
-- First scenario: error case
|
||||
-------------------------------
|
||||
local success, result = pcall(divideANumberByAnOther, 1, 0)
|
||||
|
||||
if not success then
|
||||
print("An error has occured :", result)
|
||||
end
|
||||
|
||||
-- Output: An error has occured :
|
||||
|
||||
|
||||
|
||||
-- Throw an error if the first argument is false
|
||||
-- the second argument is the error message.
|
||||
assert(type(firstvariable) = 'string', 'not a string')
|
||||
|
||||
-- TODO to continue
|
||||
-------------------------------
|
||||
-- Second scenario: valid case
|
||||
-------------------------------
|
||||
|
||||
````
|
||||
local success, result = pcall(divideANumberByAnOther, 10, 2)
|
||||
|
||||
if success then
|
||||
print("Result is :", result)
|
||||
end
|
||||
|
||||
-- Output: Result is : 5
|
||||
|
||||
|
||||
|
||||
|
||||
----------------------------------
|
||||
-- Third scenario: combine pcall with assert for critical operations
|
||||
----------------------------------
|
||||
|
||||
local function readFile(fileName)
|
||||
local fileToRead = assert(io.open(fileName, "r"), "Impossible to read the file")
|
||||
-- Continue processing if successfully opened
|
||||
fileToRead:close()
|
||||
end
|
||||
|
||||
local success, result = pcall(readFile, "data.txt") -- providing a file name that doesn't exist
|
||||
|
||||
if not success then
|
||||
print("An error has occured :", result)
|
||||
end
|
||||
|
||||
-- Output: An error has occured : Impossible to read the file
|
||||
|
||||
-- Here we use assert to robustly manage errors: if the file cannot be opened,
|
||||
-- assert triggers an error and immediately stops the program with the message.
|
||||
|
||||
|
||||
|
||||
|
||||
--------------------------------
|
||||
-- Fourth scenario: pcall with a function that returns multiple values
|
||||
--------------------------------------
|
||||
local function sumAndProduct(a, b)
|
||||
return a + b, a * b
|
||||
end
|
||||
|
||||
local success, sum, product = pcall(sumAndProduct, 3, 4)
|
||||
|
||||
if success then
|
||||
print("Sum :", sum) -- 7
|
||||
print("Product :", product) -- 12
|
||||
else
|
||||
print("Error : ", sum) -- sum would contain the error message if one occurred
|
||||
end
|
||||
|
||||
-- Reminder: pcall always returns two things:
|
||||
-- 1. A boolean indicating if the call was successful.
|
||||
-- 2. All values returned by the function if successful.
|
||||
```
|
||||
|
||||
#### xpcall (extended protected call)
|
||||
|
||||
`xpcall` is the big brother of `pcall`. It works in the same way but with an extra feature:
|
||||
we can provide a **custom error handler function** that will be executed if an error occurs during the protected function call.
|
||||
|
||||
This is especially useful when we want to customize how errors are displayed or logged, for example by adding stack traces or additional context.
|
||||
|
||||
```lua
|
||||
local function errorHandler(err)
|
||||
print("🔴 Error caught:", err)
|
||||
print(debug.traceback("Stack trace:", 2))
|
||||
end
|
||||
|
||||
local function riskyFunction()
|
||||
print("🟢 In riskyFunction")
|
||||
error("Something went wrong!")
|
||||
end
|
||||
|
||||
local function wrapper()
|
||||
print("🟡 Calling riskyFunction()")
|
||||
riskyFunction()
|
||||
print("🟡 After riskyFunction") -- never reached
|
||||
end
|
||||
|
||||
print("1. Start")
|
||||
|
||||
local success, result = xpcall(wrapper, errorHandler)
|
||||
|
||||
print("2. Execution finished")
|
||||
print("3. Success:", success)
|
||||
print("4. Result:", result)
|
||||
|
||||
-- Output:
|
||||
--[[
|
||||
1. Start
|
||||
🟡 Calling riskyFunction()
|
||||
🟢 In riskyFunction
|
||||
🔴 Error caught: Something went wrong!
|
||||
Stack trace:
|
||||
stack traceback:
|
||||
script.lua:6: in function 'riskyFunction'
|
||||
script.lua:11: in function 'wrapper'
|
||||
...
|
||||
2. Execution finished
|
||||
3. Success: false
|
||||
4. Result: nil
|
||||
]]
|
||||
|
||||
--[[
|
||||
===========================
|
||||
debug.traceback Cheatsheet
|
||||
===========================
|
||||
|
||||
debug.traceback([message], [level])
|
||||
|
||||
- message (string) → optional message shown before the stack trace
|
||||
- level (number) → determines where the trace starts in the call stack:
|
||||
|
||||
level = 0 → shows EVERYTHING, including debug.traceback itself
|
||||
level = 1 → (default) starts trace inside errorHandler() (not very useful)
|
||||
level = 2 → starts trace at the point where the error actually occurred (e.g., riskyFunction)
|
||||
|
||||
Recommended usage:
|
||||
debug.traceback("Optional message", 2)
|
||||
]]
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
@@ -476,3 +696,9 @@ g() -- Prints out '343', nothing printed before now.
|
||||
|
||||
````
|
||||
|
||||
### Credits
|
||||
|
||||
- EndMove - [contact@endmove.eu](mailto:contact@endmove.eu)
|
||||
- Varmix - [contact@varmix.fr](mailto:contact@varmix.fr)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user