The do-end blocks have to do with the problem of variable scoping. Essentially, when you use an identifier, what value does it have? For example, what numbers will be printed when we write the following program?
local x = 10
if x > 0 then
local x = 17
print(x)
end
print(x)
When it comes to local variables, Lua uses standard lexical scoping, as is well explained in section 4.2 of the Programming in Lua book. Lexical scope is very useful for a couple of reasons:
The variable scoping is static. You know just by looking at the source code what variables and functions correspond to each identifier in your code. This is opposed to the dynamic scoping you find in Bash or indirect dispatching via method calls or array lookups, where you need to think about the execution flow of the program to know what value you will end up with.
Variable scoping is limited, which helps readability and avoids some bugs:
If you declare a variable only when you are going to need to use it you can declare it and initialize it at the same time. On the other hand, if you declare all your variables at the top of the function then you might end up accidentally using one before you initialize it.
If you define a variable inside an inner scope you can't accidentally use it in outer scopes.
Lexical scoping enables some very expressive idioms when you combine it with nested functions (closures).
Usually, you don't need to worry about specifying variables scopes yourself. Functions, loops and conditionals automatically introduce new scopes and that will normally be enough for giving your variables a well constrained scope. That said, every once in a while, you might want to introduce some extra scopes out of thin air and we can use do-end for that. Programming Lua has the following example where you want to calculate the solutions of a quadratic equation and the computation has some temporaries:
do
local a2 = 2*a
local d = sqrt(b^2 - 4*a*c)
x1 = (-b + d)/a2
x2 = (-b - d)/a2
end -- scope of `a2' and `d' ends here
print(x1, x2)
Without the do-end block, a2
and d
could end up being accidentally used after they are not needed anymore:
local a2 = 2*a
local d = sqrt(b^2 - 4*a*c)
x1 = (-b + d)/a2
x2 = (-b - d)/a2
print(x1, x2)
print(a2) -- OOPS! I wanted to say "print(a)"
That said, do-end doesn't need to be used that often. If the code block is small, there is less need to hide the local variables and if the code block is big it often is a better approach to put the code block in a subroutine of its own. The times when I find that do-end shines is when you only need to do the computation once - functions can potentially be called many times but if you use a do-end block you make it clear that you are only running that piece of code once.