Lua doesn’t have true ternary operator but there is a way to mimic the behavior. This is one of the biggest issues people who aren’t used to Lua have when reading Lua code. For the most part Lua is simple and straight forward to understand. Assuming a blocky and easy to understand style is used. However, the pseudo ternary is often used and trips up people. So let’s look at it in some detail.
The core of a ternary is an if ... else statement. In Lua, we can mimic this
using and ... or with the evaluated statement. What we end of is if true
then use the code path after and or if false use the code path after the
or. Let’s look at a simple example.
if a == nil then
b
else
c
end
This is the equivalent to:
a == nil and b or c
Let’s look at more complex logic.
if a ~= nil and (1 < 5) then
b
else
c
end
We can do the same thing with the and ... or syntax. We just need to use
parenthesis like we would with an if statement to separate what applies to
which part of the statement
(a ~= nil and (1 < 2 or 4-2 > 5)) and b or c
We do need to be very careful with the separation. For this to work it always
has to be (statement) and (statement) or (statement). Notice the and and
or cannot be within parentheses. Here is a bad example that doesn’t work like
you’d expect.
a ~= nil and (b or c) .. a
In this situation we’d end up with b concatenated with a which is expected.
However, if a == nil, then we would end up with false. This statement does
not have a proper or for the false code path of the logic. Since there is
no code path Lua returns false for in this situation. The proper way to
handle sticking a tailing statement to the ternary is to put the entire
operation into parentheses.
(a ~= nil and b or c) .. a
Regardless of which branch is chosen we’ll always get the variable concatenated
with a.
Now let’s look at complex components components.
a ~= nil and (b .. c .. a) or (a .. c .. b)
a == nil and (b .. c .. a) or (a .. c .. b)
This goes back to proper use of parentheses. We have the decision, the and
and or outside of any parentheses and the code paths themselves within to
properly contain the logic.
Let’s put together some samples we can run to really demonstrate everything that’s been covered.
local a = "123"
local b = "xyz"
local c = "test"
print("Simple if")
if a == nil then
print("", b)
else
print("", c)
end
print("Equivalent simple ternary")
print("", a == nil and b or c)
print("Complex if")
if a ~= nil and (1 < 2 or 4-2 > 5) then
print("", b)
else
print("", c)
end
print("Equivalent complex ternary")
print("", (a ~= nil and (1 < 2 or 4-2 > 5)) and b or c)
print("Bad ternary")
print("", a ~= nil and (b or c) .. a)
print("", a == nil and (b or c) .. a)
print("Proper ternary")
print("", (a ~= nil and b or c) .. a)
print("", (a == nil and b or c) .. a)
print("Complex ternary")
print("", a ~= nil and (b .. c .. a) or (a .. c .. b))
print("", a == nil and (b .. c .. a) or (a .. c .. b))
Now for the output
Simple if
test
Equivalent simple ternary
test
Complex if
xyz
Equivalent complex ternary
xyz
Bad ternary
xyz123
false
Proper ternary
xyz123
test123
Complex ternary
xyztest123
123testxyz
We should keep looking at this a bit further. In the above examples only variables were used and
they were all run though a print statement. Ternary operations should be small and will typically
only use variables. Such as using it for assignment. However, functions can be run in the ture
and false branches and care needs to be taken.
Let’s look at an example with two functions.
print("Functions 1")
function b_func()
print("b_func", b)
end
function c_func()
print("c_func", c)
end
print("", true and b_func() or c_func())
You might think only function b_func would run but that’s not what happens. b_func has no
explicit return so implicitly it returns nil, which evolutes to false. true run the true
branch (b_func), but since that branch returns nil/false it will then continue on and
run the false branch (c_func). So we’ll end up with this output:
Functions 1
b_func xyz
c_func test
nil
Both functions ran which is not at all what we’d want to happen. Also notice that since
c_func also don’t have a return it returned nil and since it’s inside of a print
statement nil is printed.
This can cause problems if you’re trying to run a function that either doesn’t have
a return value or if it returns nil/false and you don’t care about the return.
The false branch (c_func) will only run if the true branch (b_func) returns
true.
This goes back to the fact this is not a true ternary but an evolution!
If we change b_func to return true like so.
function b_func()
print("b_func", b)
return true
end
Then run it again we’ll get this output.
Functions 1
b_func xyz
true
This is what we want to happen with only b_func running. We still see true printed which
is the return of the function because we’re running the return value through print.
If you’re working with functions you need to be very careful with this pattern otherwise
more than you expect might run. My advise is if you’re calling functions don’t use this
pseudo ternary and instead use a true if block. Only use this for variable assignment.
However, even with variable assignment you need to be careful with the true branch.
e = nil
g = true and e or c
What do you think g will be, nil or test (c)?
If you guessed test (c) you’re right. Again this
is an evaluation. Our expression is true, go to the true (and) branch. That branch
if false so evaluate the false (or) branch. Since it’s an or where either e or
c for the evaluation then c will run whenever after e if it is false.
The next question is how to assign nil in the true branch. As far as I’m aware
you can’t. So, use an if statement if you need nil assignment. It’s imperative that
you are sure variable used for assignment with this ternary pattern will never be nil
and any functions will either return true or a value otherwise this won’t work
like you expect.
Hopefully this makes ternaries logic can be used in Lua clear to everyone.