Good

Different

Tables and strings are indexed from 1 rather than 0.

Assigning nil as a value removes the element from a table. This is consistent with returning nil for non-existing element, so it makes no difference whether the element does not exist or exists with a value of nil . a = {b = nil} produces an empty table.

as a value removes the element from a table. This is consistent with returning for non-existing element, so it makes no difference whether the element does not exist or exists with a value of . produces an empty table. No integers as a separate numeric type; the number type represent real numbers. The next version of Lua (5.3) may change that.

No classes; object-orientation is implemented using tables and functions; inheritance is implemented using the metatable mechanism.

Method calls are implemented using object:method(args) notation, which is the same as object.method(object, args) notation, but with object evaluated only once.

notation, which is the same as notation, but with evaluated only once. nil and false are the only false values; 0, 0.0, "0" and all other values evaluate as true .

and are the only false values; 0, 0.0, "0" and all other values evaluate as . Non-equality operator is ~= (for example, if a ~= 1 then ... end ).

). not, or, and keywords used for logical operators.

keywords used for logical operators. Assignments are statements, which means there is no a=b=1 or if (a=1) then ... end .

or . No a+=1 , a++ , or similar shorthand forms.

, , or similar shorthand forms. No continue statement, although there is an explanation and a number of alternatives, like using repeat break until true inside the loop to break out of or a goto statement introduced in Lua 5.2.

statement, although there is an explanation and a number of alternatives, like using inside the loop to break out of or a statement introduced in Lua 5.2. No switch statement.

statement. Brackets may be required in some contexts; for example, a = {}; a.field works, but {}.field doesn't; the latter needs to be specified as ({}).field .

works, but doesn't; the latter needs to be specified as . A control variable in a loop is localized by default and is not available after the loop.

Limit and step values in the numeric for loop are cached; this means that in for i = init(), limit(), step() do ... end all three functions init , limit , and step are called once before the loop is executed.

loop are cached; this means that in all three functions , , and are called once before the loop is executed. Conditionals and other control structures require no brackets.

Strings and numbers are automatically converted (if string is used where a number is expected and vice versa), but not in equality/inequality comparisons: 0 == "0" is false , {} ~= 1 is true , and foo["0"] and foo[0] refer to two different keys in the table; other relational operators generate errors on comparing values of different types.

is , is , and and refer to two different keys in the table; other relational operators generate errors on comparing values of different types. Both commas and semicolons can be used in table literals; both can be placed before the closing curly bracket as an optional separator: a = {a = 1, b = 2, } .

. Smaller than expected number of components that are available "out of the box"; some people see this as "batteries not included". This is the other side of having a compact and portable core and is well compensated by having LuaRocks and libraries like Penlight.

Bad

Ugly

Number of elements in a table is not easy to get and the result depends on how you do this (or what you mean by "length"). This is probably not surprising, given how powerful tables are in Lua and the fact that they support flexible indexing (by numbers and any other Lua type except nil ). Tables in Lua have two parts: an "array/vector" part (generated with t = {1, 2, 3} ) and a "hash" part (generated with t = {a = "foo", ["b"] = 2} ); the two can be flexibly combined. #table returns the length of the shortest "array/vector" part (without any gaps) and table.maxn(t) returns the longest "array/vector" part (this function is removed in Lua 5.2). The "hash" part doesn't have a defined length. Both parts can be iterated over using the pairs method, which allows you to count the number of elements in them. However, print(#{1, 2, nil, 3}) prints 4 and not 2 as one may expect, whereas print(#{1, 2, nil, 3, nil}) prints 2. I'm sure there is a good reasonable explanation for this, but for now it is in the "ugly" bucket. [Updated 11/17/2012] As FireFly noted in the comments, in Lua 5.2 the length operator is only defined for tables that don't have holes in them.

). Tables in Lua have two parts: an "array/vector" part (generated with ) and a "hash" part (generated with ); the two can be flexibly combined. returns the length of the shortest "array/vector" part (without any gaps) and returns the longest "array/vector" part (this function is removed in Lua 5.2). The "hash" part doesn't have a defined length. Both parts can be iterated over using the method, which allows you to count the number of elements in them. However, prints 4 and not 2 as one may expect, whereas prints 2. I'm sure there is a good reasonable explanation for this, but for now it is in the "ugly" bucket. As FireFly noted in the comments, in Lua 5.2 the length operator is only defined for tables that don't have holes in them. return statement can't be used if it's not the last statement in a block; in other words, function foo() print(1); return; print(2) end will trigger an error 'end' expected... or unexpected symbol near <whatever statement you have after 'return'> (depending on whether you have semicolon after return or not). Not that anyone would want use this for anything other than debugging, but I got bitten by it couple of times. I would have put this in the "different" category, but I find it inconsistent that I can't use return , but can use do return end in exactly the same place. [Updated 5/19/2012] This also applies to break statement, although in Lua 5.2 break is no longer required to be the last statement in a block.

statement can't be used if it's not the last statement in a block; in other words, will trigger an error or (depending on whether you have semicolon after or not). Not that anyone would want use this for anything other than debugging, but I got bitten by it couple of times. I would have put this in the "different" category, but I find it inconsistent that I can't use , but can use in exactly the same place. This also applies to statement, although in Lua 5.2 is no longer required to be the last statement in a block. Only one value is returned from a function if it's not the last one in a list; for example: function f123() return 1, 2, 3 end function f456() return 4, 5, 6 end print(f123(), f456()) -- prints 1, 4, 5, 6 print(f456(), f123()) -- prints 4, 1, 2, 3 The related behavior of return is also affected by this rule: return f456() returns three values, but return (f456()) returns only one value (note the extra pair of parentheses). This matches the overall simplicity of the language and is well documented, but I still find it to be a bit ugly (although ugliness as beauty is in the eye of the beholder).

Overall, I have so far enjoyed the simplicity and consistency of the language, although there are few things I wish were done a bit differently. My eight-year-old son also picked Lua syntax quickly, which reminded me a lot about my experience with Turbo Pascal decades ago.