Home

Awesome

Lua Style Guide

This style guides lists the coding conventions used in the LuaRocks project. It does not claim to be the best Lua coding style in the planet, but it is used successfully in a long-running project, and we do provide rationale for many of the design decisions listed below.

The list of recommendations in this document was based on the ones mentioned in the following style guides: (sometimes copying material verbatim, sometimes giving the opposite advice! :) )

Indentation and formatting

for i, pkg in ipairs(packages) do
   for name, version in pairs(pkg) do
      if name == searched then
         print(version)
      end
   end
end

Rationale: There is no agreement in the Lua community as for indentation, so 3 spaces lies nicely as a middle ground between the 2-space camp and the 4-space camp. Also, for a language that nests with do/end blocks, it produces pleasant-looking block-closing staircases, as in the example above.

Documentation

--- Load a local or remote manifest describing a repository.
-- All functions that use manifest tables assume they were obtained
-- through either this function or load_local_manifest.
-- @param repo_url string: URL or pathname for the repository.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table or (nil, string, [string]): A table representing the manifest,
-- or nil followed by an error message and an optional error code.
function manif.load_manifest(repo_url, lua_version)
   -- code
end
-- TODO: implement method
local function something()
   -- FIXME: check conditions
end

Variable names

for _, item in ipairs(items) do
   do_something_with_item(item)
end
-- bad
local OBJEcttsssss = {}
local thisIsMyObject = {}
local c = function()
   -- ...stuff...
end

-- good
local this_is_my_object = {}

local function do_that_thing()
   -- ...stuff...
end

Rationale: The standard library uses lowercase APIs, with joinedlowercase names, but this does not scale too well for more complex APIs. snake_case tends to look good enough and not too out-of-place along side the standard APIs.

for _, name in pairs(names) do
   -- ...stuff...
end
-- bad
local function evil(alignment)
   return alignment < 100
end

-- good
local function is_evil(alignment)
   return alignment < 100
end

Rationale: "Sparingly", since Lua does not have real constants. This notation is most useful in libraries that bind C libraries, when bringing over constants from C.

Tables

local player = {
   name = "Jack",
   class = "Rogue",
}

Rationale: This makes the structure of your tables more evident at a glance. Trailing commas make it quicker to add new fields and produces shorter diffs.

table = {
   ["1394-E"] = val1,
   ["UTF-8"] = val2,
   ["and"] = val2,
}

Strings

local name = "LuaRocks"
local sentence = 'The name of the program is "LuaRocks"'

Rationale: Double quotes are used as string delimiters in a larger number of programming languages. Single quotes are useful for avoiding escaping when using double quotes in literals.

Line lengths

Rationale: No one works on VT100 terminals anymore. If line lengths are a proxy for code complexity, we should address code complexity instead of using line breaks to fit mind-bending statements over multiple lines.

Function declaration syntax

-- bad
local nope = function(name, options)
   -- ...stuff...
end

-- good
local function yup(name, options)
   -- ...stuff...
end
-- bad
local function is_good_name(name, options, arg)
   local is_good = #name > 3
   is_good = is_good and #name < 30

   -- ...stuff...

   return is_good
end

-- good
local function is_good_name(name, options, args)
   if #name < 3 or #name > 30 then
      return false
   end

   -- ...stuff...

   return true
end

Function calls

-- bad
local data = get_data"KRP"..tostring(area_number)
-- good
local data = get_data("KRP"..tostring(area_number))
local data = get_data("KRP")..tostring(area_number)

Rationale: It is not obvious at a glace what the precedence rules are when omitting the parentheses in a function call. Can you quickly tell which of the two "good" examples in equivalent to the "bad" one? (It's the second one).

local an_instance = a_module.new {
   a_parameter = 42,
   another_parameter = "yay",
}

Rationale: The use as in a_module.new above occurs alone in a statement, so there are no precedence issues.

Table attributes

local luke = {
   jedi = true,
   age = 28,
}

-- bad
local is_jedi = luke["jedi"]

-- good
local is_jedi = luke.jedi
local vehicles = load_vehicles_from_disk("vehicles.dat")

if vehicles["Porsche"] then
   porsche_handler(vehicles["Porsche"])
   vehicles["Porsche"] = nil
end
for name, cars in pairs(vehicles) do
   regular_handler(cars)
end

Rationale: Using dot notation makes it clearer that the given key is meant to be used as a record/object field.

Functions in tables

local my_module = {}

function my_module.a_function(x)
   -- code
end
local version_mt = {
   __eq = function(a, b)
      -- code
   end,
   __lt = function(a, b)
      -- code
   end,
}

Rationale: Metatables contain special behavior that affect the tables they're assigned (and are used implicitly at the call site), so it's good to be able to get a view of the complete behavior of the metatable at a glance.

This is not as important for objects and modules, which usually have way more code, and which don't fit in a single screen anyway, so nesting them inside the table does not gain much: when scrolling a longer file, it is more evident that check_version is a method of Api if it says function Api:check_version() than if it says check_version = function() under some indentation level.

Variable declaration

-- bad
superpower = get_superpower()

-- good
local superpower = get_superpower()

Rationale: Not doing so will result in global variables to avoid polluting the global namespace.

Variable scope

-- bad
local function good()
   local name = get_name()

   test()
   print("doing stuff..")

   --...other stuff...

   if name == "test" then
      return false
   end

   return name
end

-- good
local bad = function()
   test()
   print("doing stuff..")

   --...other stuff...

   local name = get_name()

   if name == "test" then
      return false
   end

   return name
end

Rationale: Lua has proper lexical scoping. Declaring the function later means that its scope is smaller, so this makes it easier to check for the effects of a variable.

Conditional expressions

-- bad
if name ~= nil then
   -- ...stuff...
end

-- good
if name then
   -- ...stuff...
end
local function default_name(name)
   -- return the default "Waldo" if name is nil
   return name or "Waldo"
end

local function brew_coffee(machine)
   return (machine and machine.is_loaded) and "coffee brewing" or "fill your water"
end

Note that the x and y or z as a substitute for x ? y : z does not work if y may be nil or false so avoid it altogether for returning booleans or values which may be nil.

Blocks

-- good
if test then break end

-- good
if not ok then return nil, "this failed for this reason: " .. reason end

-- good
use_callback(x, function(k) return k.last end)

-- good
if test then
  return false
end

-- bad
if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then do_other_complicated_function() end

-- good
if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then
   do_other_complicated_function() 
   return false 
end
-- bad
local whatever = "sure";
a = 1; b = 2

-- good
local whatever = "sure"
a = 1
b = 2

Spacing

--bad
-- good
-- bad
local x = y*9
local numbers={1,2,3}
numbers={1 , 2 , 3}
numbers={1 ,2 ,3}
local strings = { "hello"
                , "Lua"
                , "world"
                }
dog.set( "attr",{
  age="1 year",
  breed="Bernese Mountain Dog"
})

-- good
local x = y * 9
local numbers = {1, 2, 3}
local strings = {
   "hello",
   "Lua",
   "world",
}
dog.set("attr", {
   age = "1 year",
   breed = "Bernese Mountain Dog",
})
-- bad
local my_table = {
                    "hello",
                    "world",
                 }
using_a_callback(x, function(...)
                       print("hello")
                    end)

-- good
local my_table = {
   "hello",
   "world",
}
using_a_callback(x, function(...)
   print("hello")
end)

Rationale: This keep indentation levels aligned at predictable places. You don't need to realign the entire block if something in the first line changes (such as replacing x with xy in the using_a_callback example above).

-- okay
local message = "Hello, "..user.."! This is your day # "..day.." in our platform!"

Rationale: Being at the baseline, the dots already provide some visual spacing.

-- bad
local function hello ( name, language )
   -- code
end

-- good
local function hello(name, language)
   -- code
end
-- bad
local function foo()
   -- code
end
local function bar()
   -- code
end

-- good
local function foo()
   -- code
end

local function bar()
   -- code
end
-- bad
local a               = 1
local long_identifier = 2

-- good
local a = 1
local long_identifier = 2

Rationale: This produces extra diffs which add noise to git blame.

-- okay
sys_command(form, UI_FORM_UPDATE_NODE, "a",      FORM_NODE_HIDDEN,  false)
sys_command(form, UI_FORM_UPDATE_NODE, "sample", FORM_NODE_VISIBLE, false)

Typing

function manif.load_manifest(repo_url, lua_version)
   assert(type(repo_url) == "string")
   assert(type(lua_version) == "string" or not lua_version)

   -- ...
end

Rationale: This is a practice adopted early on in the development of LuaRocks that has shown to be beneficial in many occasions.

-- bad
local total_score = review_score .. ""

-- good
local total_score = tostring(review_score)

Errors

Modules

Follow these guidelines for writing modules. In short:

local bar = require("foo.bar") -- requiring the module

bar.say("hello") -- using the module
-- bad
local skt = require("socket")

Rationale: Code is much harder to read if we have to keep going back to the top to check how you chose to call a module.

--- @module foo.bar
local bar = {}

That is, local function helper_foo() means that helper_foo is really local.

function bar.say(greeting)
   print(greeting)
end

Rationale: Visibility rules are made explicit through syntax.

Rationale: Modules should return tables in order to be amenable to have their contents inspected via the Lua interactive interpreter or other tools.

-- bad
local mp = require "MessagePack"
mp.set_integer("unsigned")

and do something like this instead:

-- good
local messagepack = require("messagepack")
local mpack = messagepack.new({integer = "unsigned"})
-- bad
local bla = require "bla"

-- good
local bla = require("bla")

Rationale: This makes it explicit that require is a function call and not a keyword. Many other languages employ keywords for this purpose, so having a "special syntax" for require would trip up Lua newcomers.

OOP

--- @module myproject.myclass
local myclass = {}

-- class table
local MyClass = {}

function MyClass:some_method()
   -- code
end

function MyClass:another_one()
   self:some_method()
   -- more code
end

function myclass.new()
   local self = {}
   setmetatable(self, { __index = MyClass })
   return self
end

return myclass

Rationale: It’s easy to see in the code above that the functions with MyClass in their signature are methods. A deeper discussion of the design rationale for this is found here.

-- bad 
my_object.my_method(my_object)
-- good
my_object:my_method()

Rationale: This makes it explicit that the intent is to use the function as an OOP method.

Rationale: The garbage collector performs automatic memory management, dealing with memory only. There is no guarantees as to when the garbage collector will be invoked, and memory pressure does not correlate to pressure on other resources.

File structure

Static checking

It's best if code passes luacheck. If it does not with default settings, it should provide a .luacheckrc with sensible exceptions.

Rationale: Git is paranoid about trailing whitespace due to the patch-file email-based workflow inherited from the Linux kernel mailing list. When using the Git tool proper, exceeding whitespace makes no difference whatsoever except for being highlighted by Git's coloring (for the aforementioned reasons). Git's pedantism about it has spread over the year to the syntax highlighting of many text editors and now everyone says they hate trailing whitespace without being really able to answer why (the actual cause being that tools complain to them about it, for no good reason).

if warning >= 600 and warning <= 699 then
   print("no whitespace warnings")
elseif warning == 542 then
   -- pass
else
   print("got a warning: "..warning)
end

Rationale: This avoids writing negated conditions in the final fallback case, and it's easy to add another case to the construct without having to edit the fallback.