This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.


15.2 – Privacy

Sometimes, a package exports all its names; that is, any client of the package can use them. Usually, however, it is useful to have private names in a package, that is, names that only the package itself can use. A convenient way to do that in Lua is to define those private names as local variables. For instance, let us add to our example a private function that checks whether a value is a valid complex number. Our example now looks like this:

    local P = {}
    complex = P
    
    local function checkComplex (c)
      if not ((type(c) == "table") and
         tonumber(c.r) and tonumber(c.i)) then
        error("bad complex number", 3)
      end
    end
    
    function P.add (c1, c2)
      checkComplex(c1);
      checkComplex(c2);
      return P.new(c1.r + c2.r, c1.i + c2.i)
    end
    
      ...
    
    return P

What are the pros and cons of this approach? All names in a package live in a separate namespace. Each entity in a package is clearly marked as public or private. Moreover, we have real privacy: Private entities are inaccessible outside the package. A drawback of this approach is its verbosity when accessing other public entities inside the same package, as every access still needs the prefix P. A bigger problem is that we have to change the calls whenever we change the status of a function from private to public (or from public to private).

There is an interesting solution to both problems at once. We can declare all functions in our package as local and later put them in the final table to be exported. Following this approach, our complex package would be like this:

    local function checkComplex (c)
      if not ((type(c) == "table")
         and tonumber(c.r) and tonumber(c.i)) then
        error("bad complex number", 3)
      end
    end
    
    local function new (r, i) return {r=r, i=i} end
    
    local function add (c1, c2)
      checkComplex(c1);
      checkComplex(c2);
      return new(c1.r + c2.r, c1.i + c2.i)
    end
    
      ...
    
    complex = {
      new = new,
      add = add,
      sub = sub,
      mul = mul,
      div = div,
    }

Now we do not need to prefix any calls, so that calls to exported and private functions are equal. There is a simple list at the end of the package that defines explicitly which names to export. Most people find more natural to have this list at the beginning of the package, but we cannot put the list at the top, because we must define the local functions first.