Ideally, functions should treat their arguments as immutable and copy by-value every argument that is to be changed to a localized variable -- yes, that includes scalars too. It makes the code easier to debug when the scope and state of arguments is always constant and never in doubt. Of course, this doesn't apply to arguments that are never changed.
1. A simple helper function
Below is a simple helper function that I use extensively in the just_test_tribute subgame. I kind of wish it part of Minetest builtin since it would help to overcome a lot of little bugs, some of which can be very difficult to detect and trace. But, I digress :)
- t2 = by_value( t1 )
- t1 - the table to be copied, either a shallow hash or an ordered array
- t2 - the copied table
Code: Select all
-----------------------------------------------------------------
-- pass hashes and arrays by value during function calls
-- by sorcerykid (CC0 1.0 Universal)
-----------------------------------------------------------------
function by_value( t1 )
local t2 = { }
if #t1 > 0 then
-- ordered copy of arrays
t2 = { unpack( t1 ) }
else
-- shallow copy of hashes
for k, v in pairs( t1 ) do t2[ k ] = v end
end
return t2
end
In this example, we declare a function that takes two arguments, both of which are tables. There is one flaw, however. We are modifying the node table, which is not a behavior that anybody would expect.
Code: Select all
function replace_node( pos, node )
node.param2 = 0 -- set the rotation to zero
minetest.swap_node( pos, node )
end
Code: Select all
function replace_node( pos, node )
node = by_value( node )
node.param2 = 0 -- set the rotation to zero
minetest.swap_node( pos, node )
end
If, for some reason you still need access to the original table for other purposes, then you could assign the copied table to a new local variable, such as new_node, thus keeping the argument itself in tact.
3. Inserting tables into arrays or hashes.
Of course function arguments aren't the only cause for concern. Inserting tables within other tables can result in multiple references spanning across a variety of functions and modules. Changes to the original table will no longer be isolated to just that one table. And it will be almost impossible to account for every separate reference to that table should you decide to make a change in only one place! You've heard of the butterfly effect, right? This is most likely not what you are intending.
In this example, we register two nodes with the same groups. However, we want to exclude the second node from the craft guide.
Code: Select all
local unobtanium_groups = {cracky=3, oddly_breakable_by_hand=1}
minetest.register_node("unobtanium:common", {
description = S("Unobtanium Common Block"),
tiles = {"unobtanium_common.png"},
groups = unobtanium_groups,
sounds = default.node_sound_glass_defaults(),
})
unobtanium_groups.not_in_creative_inventory = 1
minetest.register_node("unobtanium:rare", {
description = S("Unobtanium Rare Block"),
tiles = {"unobtanium_rare.png"},
groups = unobtanium_groups,
sounds = default.node_sound_glass_defaults(),
})
Code: Select all
local unobtanium_groups = {cracky=3, oddly_breakable_by_hand=1}
minetest.register_node("unobtanium:common", {
description = S("Unobtanium Common Block"),
tiles = {"unobtanium_common.png"},
groups = by_value(unobtanium_groups),
sounds = default.node_sound_glass_defaults(),
})
unobtanium_groups.not_in_creative_inventory = 1
minetest.register_node("unobtanium:rare", {
description = S("Unobtanium Rare Block"),
tiles = {"unobtanium_rare.png"},
groups = by_value(unobtanium_groups),
sounds = default.node_sound_glass_defaults(),
})
4. Conclusion.
I really hope that this guide helps mod authors to avoid the various snafus that can crop up while working with tables in Lua. Generally speaking, you don't need to go "copy-crazy", since that is an inefficient use of memory. But at the same time, you should still use discretion when inserting tables into arrays or hashes or passing tables as function arguments to avoid the "gotchas" described above.
Thanks for reading!