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
|
### 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
|
### 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