Using assert() is fine, but…
TL;DR
It’s Fine if you use assert()
like this:
local function printName(name)
assert(type(name) == "string", "Argument must be a string!")
print(name)
end
It’s Bad if you use assert()
like this:
local function printName(name)
assert(type(name) == "string", "Argument must be a string, but got: " .. tostring(name))
print(name)
end
If you make a “custom” assert function like this, it is just the Same as the lua assert()
:
function(condition, errorMessage)
if not (condition)
error(errorMessage, 2)
end
end
assert()
isn’t the problem but frequent string formatting is
If you use assert()
a lot in your codebase, you will probably notice the script activity percentage mysteriously surge. The culprit is costly evaluation of string concatenation or string.format
in the second argument for assert
.
assert
is a regular function. Therefore, Lua always evaluates its arguments before calling the function.
You can check the script activity percentage by enable the Script Performance view:
image credit - @patfre
What is assert()?
Read this post if you don’t know what assert()
is:
Potential Solutions
1. Use if-else block to replace assert()
Your code might look like this:
local function doSomethingWithANumber(number)
assert(type(number) == "number", "Argument 1 must be a number, got " .. tostring(number))
end
The following code does the same thing but with better performance
local function doSomethingWithANumber(number)
if type(number) ~= "number" then
error("Argument 1 must be a number got " .. tostring(number))
end
end
Explanation:
Using assert()
with a second string argument is less performant because the string concatenation
"Argument 1 must be a number, got " .. tostring(number)
is always evaluated regardless of the given condition type(number) == "number"
.
However, if you use if-else
block,
error("Argument 1 must be a number got " .. tostring(number))
will not be evaluated unless the given error condition type(number) ~= "number"
is met.
Less evaluations means less code to execute, which means better performance.
Proof with Benchmarking
The result showed using if-else
was about 70% more performant than using assert()
.
(Feel free to point out any flaws in the test I wrote, my goal is to make the code of our games to run faster. )
Code:
local function slowMaths(numberA, options)
assert(
type(numberA) == "number" and numberA > 0,
"Bad Argument #1 numberA, got: " .. numberA
)
assert(
type(options) == "table"
and type(options.NumberB) == "number"
and options.NumberB > 0,
"Bad Argument #2 options, got: " .. tostring(options)
)
return numberA * options.NumberB
end
local function quickMaths(numberA, options)
if not (type(numberA) == "number" and numberA > 0) then
error("Bad Argument #1 numberA, got: " .. numberA)
end
if
not (
type(options) == "table"
and type(options.NumberB) == "number"
and options.NumberB > 0
)
then
error("Bad Argument #2 options, got: " .. tostring(options))
end
return numberA * options.NumberB
end
local function test(testCaseText, mathFunction)
local start = os.clock()
for i = 1, 10000 do
mathFunction(i, { NumberB = i + 1 })
end
local finish = os.clock()
local delta = finish - start
print(testCaseText .. " took: " .. delta .. " sec")
return delta
end
print('Waiting for "Play Solo" to be stable.')
wait(5) -- Wait for Play Solo to be stable enough to run tests
local result1 = test("slowMaths with assert()", slowMaths)
local result2 = test("quickMaths with if-else block", quickMaths)
local fasterPercentage = ((result2 - result1) / result1 * 100)
print(
"quickMaths with if-else block is "
.. math.abs(fasterPercentage)
.. "% "
.. (fasterPercentage < 0 and "faster" or "slower")
)
Output:
Waiting for "Play Solo" to be stable.
slowMaths with assert() took: 0.022925248002139 sec
quickMaths with if-else block took: 0.0069385550013976 sec
quickMaths with if-else block is 69.734002438053% faster
2. Use Luau’s type checking syntax
local function doSomethingWithANumber(number: number)
-- do something with a number here
end
You can read more about it here:
Ending
This is my first post, thanks for reading.
Edit:I didn’t know before I wrote this.
Apparently, someone else has already talked about this issue in a similar post. :