Lima Lua style guide

June 7, 2017 ยท View on GitHub

About this style guide

How to read this style guide

Refer to RFC 2119 to interpret MUST, SHOULD and MAY.

Inspiration

Non-cosmetic

Ternaries

Ternaries with litterals are OK:

local x = y and "yes" or "no"

Ternaries with a variable as the second element should be avoided. Remember:

1 and false or 2 -- do not do this, evaluates to 2

Modules

Modules MUST NOT set or access any global variable when required.

Modules MUST always return a table when required. If you would rather like to return a function you MAY set the __call metamethod on the table instead.

Internal modules that can be used in different contexts MAY use the lima namespace. Modules that are released as Open Source SHOULD NOT be namespaced.

When a module behaves like a class, it SHOULD expose a constructor called new.

local dog = require "lima.example.dog"
local my_dog = dog.new()

A module SHOULD NOT have state. If a module needs configuration, turn it into a factory. For instance, this (from an Open Source module we did not write):

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

should have this interface instead:

local messagepack = require "MessagePack"
local mp = messagepack.new({integer = "unsigned"})

luacheck

Your code MUST pass luacheck. If it does not with default settings, you must provide a .luacheckrc with sensible exceptions.

errors

Functions that can fail for reasons that are expected (e.g. I/O) SHOULD return nil and a (string) error message on error, possibly followed by other return values such as an error code, like the Lua io library, LuaSocket and luaposix do.

On errors such as API misuse, an error SHOULD be thrown, either with error() or assert().

A different error handling convention MAY be used in parts of the code where it makes sense, for instance in FUSE code where FLU expects you to throw POSIX error codes.

preconditions

You SHOULD verify the type and shape of arguments passed to public functions in your modules using assert. You MAY also do so for private functions. You MAY avoid those checks in functions that are intended to be very fast.

local function is_posint(x)
    return (math.floor(x) == x) and (x >= 0)
end

local function foo(s, t)
    assert(type(s) == "string")
    t = t or {}
    t.x = t.x or 42
    assert(
        type(t) == "table" and
        is_posint(t.x)
    )
end

Cosmetic

Homogeneity

Style homogeneity within a file trumps everything else, including all the rules below.

If you import open source code and modify it slightly, you MUST stick to the style of the original code. If you modify it a lot and do not intend to merge any upstream changes ever, you MAY convert the code to Lima style. You MUST do this in a separate commit which does not include any other modification. You SHOULD use "cosmetics" as the commit message.

Tests and other DSLs

Tests can be considered as a DSL, and as such are not required to follow this style guide strictly. It MAY make sense to have different rules in a DSL if they improve legibility.

Whitespace

All new code MUST be indented with four spaces.

Most operators SHOULD be surrounded with spaces. Commas MUST have no whitespace before them and some after them.

local x, y = 1, 2

You MUST NOT leave a space between the name of a function and the opening parenthesis of the arguments (despite the fact that the Lua manual does this).

You MUST leave a space between a function and its litteral string or table argument when called with no parentheses.

You SHOULD NOT use whitespace to align things vertically.

-- Do not do this.
local chunky = 4
local foo    = 65

You SHOULD keep line length below 80 characters. When needed, you SHOULD indent tables and function arguments like this:

some_function(
    argument_1,
    argument_2,
    a_very_long_argument,
    other_arguments...
)

Alternatively, you MAY put several arguments on the same line, but you SHOULD always indent with four spaces and leave the closing parenthesis on its own line.

some_function(
    argument_1, argument_2, a_very_long_argument,
    other_arguments...
)

Defining and calling functions

In Lua, function f(...) is syntactic sugar for f = function(...) and local function f(...) is syntactic sugar for local f; f = function(...).

You SHOULD use this sugar only in its second form, when declaring a new local function.

You MUST NOT use the sugar:

  • to define global functions;
  • to define functions in tables;
  • to define a function whose local is already declared.
local g
local function f(...) end
g = function(...) end
local t = {
    f = f,
    h = function(...) end,
}
t.g = g
t.i = function(...) end

In Lua, x:f(...) is syntactic sugar for x.f(x, ...).

You MUST NOT use the colon sugar for method definition.

local t = {n = 42}
t.f = function(self) print(self.n) end -- OK
function t:f() print(self.n) end -- not OK

You MUST use the sugar for method calls.

t:f() -- OK
t.f(t) -- not OK

Note that this is completely different from using this in a module:

local function f(self) ... end

local g = function(self)
    f(self) -- and not self:f()
end

The above does not have the same meaning (it creates a closure, meaning that the user cannot override the definition of f as called in g). This pattern is perfectly OK. What would not be OK would be self.f(self).

Also, of course when using pcall you don't have a choice:

pcall(self.f, self)

Remember: try to keep a homogeneous style within a single file or module.

Function calls

You SHOULD NOT omit parenthesis for functions that take a unique string litteral argument. An exception to this rule is require, which you SHOULD use like this.

You SHOULD NOT omit parenthesis for functions that take a unique table argument on a single line. You MAY do so for table arguments that span several lines.

local a_module = require "a_module"
local an_instance = a_module.new {
    a_parameter = 42,
    another_parameter = "yay",
}
an_instance:a_function("a_string")

Semicolons and commas

In general you SHOULD NOT use semicolons. In particular, the separator between table elements MUST always be a comma.

If you write a table on multiple lines, you SHOULD add a comma after the last element. If you write a table on a single line, you SHOULD NOT.

local t = {
    x = 1,
    y = 2,
}

local t = {x = 1, y = 2}

Variable names

You SHOULD use short variable names (even single letters) for variables with limited scope and slightly longer variable names for functions parameters and for variables that may be used far from their declaration (but you SHOULD NOT have many such variables).

You SHOULD use short names for functions and methods intended to be used frequently and longer, more descriptive names for functions and methods that will be used more rarely.

In general, you SHOULD use snake_case for locals, functions, methods, table fields and module names. You SHOULD use UPPER_CASE for constants.

You SHOULD NOT use CamelCase and you MUST NOT use snakeCase.

Anonymous functions

You SHOULD NOT use anonymous functions if they span several lines.

You MAY do so if they fit on a single line.

local function my_handler(...)
    --[[ do something long ]]
end

local t = {
    handler_1 = my_handler,
    handler_2 = function(...) end,
}

rpc_call("SOME_RPC", my_handler)
rpc_call("SOME_RPC", function(...) print("RPC called") end)