feat: add error handling part #3

Merged
Varmix merged 3 commits from error-handling into master 2025-04-19 23:18:20 +02:00
2 changed files with 232 additions and 8 deletions

View File

@@ -58,7 +58,8 @@ APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright [2023] [EndMove]
Copyright [2025] [Varmix]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

237
README.md
View File

@@ -406,19 +406,236 @@ 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 functions 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 :
-------------------------------
-- Second scenario: valid case
-------------------------------
local success, result = pcall(divideANumberByAnOther, 10, 2)
if success then
print("Result is :", result)
end
-- Output: Result is : 5
-- Throw an error if the first argument is false
-- the second argument is the error message.
assert(type(firstvariable) = 'string', 'not a string')
---------------------------------------------------------------------
-- Third scenario: combine pcall with assert for critical operations
---------------------------------------------------------------------
-- TODO to continue
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 +693,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)