Standard library
The Lua standard library is small but substantial — admit the principal modules: string (string manipulation, patterns), table (table operations), math (numeric functions), os (operating-system facilities), io (file I/O), package (module loading), coroutine (coroutines), debug (introspection and debugging). The standard library is intentionally minimal — admit substantial extension via libraries like Penlight (substantial general-purpose extensions), LuaSocket (networking), LuaFileSystem (file system extensions), cjson (JSON), and many others. The combination — small core library, the conventional extension via LuaRocks libraries, the embedding model where host applications expose substantial APIs — is the substance of Lua’s runtime library.
This tour points out the principal standard-library modules and their conventional uses.
string
Treated in Strings.
The principal functions:
string.len(s) -- length (same as #s)
string.upper(s)
string.lower(s)
string.reverse(s)
string.rep(s, n, sep) -- repeat (5.3+ admits sep)
string.sub(s, i, j) -- substring
string.byte(s, i) -- byte at i
string.char(n) -- char from byte
string.format(fmt, ...) -- printf-style
string.find(s, pattern)
string.match(s, pattern)
string.gmatch(s, pattern) -- iterator
string.gsub(s, pattern, repl) -- substitute
For method-style:
local s = "hello"
s:upper() -- equivalent to string.upper(s)
s:format(...)
s:rep(3)
table
The principal functions:
table.insert(t, [pos,] value) -- insert (default at end)
table.remove(t, [pos]) -- remove (default last)
table.concat(t, [sep, [i, [j]]]) -- join
table.sort(t, [comp]) -- in-place sort
table.unpack(t, [i, [j]]) -- 5.2+ (was unpack in 5.1)
table.pack(...) -- 5.2+ (table from varargs)
table.move(a1, f, e, t [, a2]) -- 5.3+ (move/copy range)
table.insert and table.remove
local arr = {10, 20, 30}
table.insert(arr, 40) -- {10, 20, 30, 40}
table.insert(arr, 1, 0) -- {0, 10, 20, 30, 40}
table.remove(arr) -- {0, 10, 20, 30}; returns 40
table.remove(arr, 1) -- {10, 20, 30}; returns 0
table.concat
local arr = {"a", "b", "c"}
table.concat(arr) -- "abc"
table.concat(arr, ", ") -- "a, b, c"
table.concat(arr, ", ", 2, 3) -- "b, c"
The function admits substantial efficiency over the .. operator in loops.
table.sort
local arr = {3, 1, 4, 1, 5, 9, 2, 6}
table.sort(arr) -- {1, 1, 2, 3, 4, 5, 6, 9}
table.sort(arr, function(a, b) return a > b end) -- descending
local people = {{name = "Bob", age = 25}, {name = "Alice", age = 30}}
table.sort(people, function(a, b) return a.age < b.age end)
table.pack and table.unpack
local args = table.pack("a", "b", "c") -- {n=3, "a", "b", "c"}
local n = args.n -- 3 (extra .n field)
local function f(a, b, c) return a, b, c end
print(f(table.unpack({"x", "y", "z"}))) -- x, y, z
-- Slice:
table.unpack({1, 2, 3, 4, 5}, 2, 4) -- 2, 3, 4
The table.pack admits storing varargs (with explicit count); table.unpack admits unpacking back to varargs.
math
math.pi -- 3.14159...
math.huge -- infinity
math.maxinteger -- 9223372036854775807 (5.3+)
math.mininteger -- -9223372036854775808 (5.3+)
math.abs(-5) -- 5
math.ceil(3.1) -- 4
math.floor(3.9) -- 3
math.max(1, 2, 3) -- 3
math.min(1, 2, 3) -- 1
math.modf(3.7) -- 3.0, 0.7 (integer and fractional parts)
math.sqrt(16) -- 4.0
math.exp(1) -- 2.71828...
math.log(math.exp(1)) -- 1
math.log(100, 10) -- 2 (with base)
math.pow(2, 10) -- 1024 (5.1; replaced by ^ in 5.2+)
math.sin(0)
math.cos(0)
math.tan(0)
math.asin(1)
math.atan(1)
math.atan(y, x) -- atan2 (5.3+ as overload)
math.random() -- [0, 1)
math.random(10) -- [1, 10]
math.random(5, 15) -- [5, 15]
math.randomseed(os.time()) -- seed (typically with os.time())
-- 5.3+:
math.tointeger(3.0) -- 3
math.tointeger(3.5) -- nil
math.type(3) -- "integer"
math.type(3.14) -- "float"
os
Operating-system facilities:
os.time() -- seconds since epoch
os.time({year=2026, month=1, day=15}) -- specific time
os.date() -- "Wed Jan 15 10:00:00 2026"
os.date("%Y-%m-%d %H:%M:%S") -- formatted
os.date("*t", time) -- table form
os.date("!*t", time) -- UTC table
os.difftime(t2, t1) -- seconds between
os.clock() -- CPU time (seconds)
os.getenv("HOME") -- environment variable
os.execute("ls") -- run shell command
os.exit(0) -- exit with code
os.tmpname() -- temp filename
os.remove(path) -- delete file
os.rename(old, new) -- rename
Date table format:
local t = os.date("*t")
-- t = {year=2026, month=1, day=15, hour=10, min=0, sec=0, wday=4, yday=15, isdst=false}
local time = os.time(t)
io
Treated in I/O.
io.write("hello\n")
io.read() -- read a line from stdin
print("hello") -- alias for io.write + newline
local f = io.open("file.txt", "r")
local content = f:read("*a") -- entire file
f:close()
for line in io.lines("file.txt") do
print(line)
end
package
Treated in Modules and packaging.
require("modulename")
package.loaded["modulename"] -- cache
package.path -- search path for .lua
package.cpath -- search path for C modules
package.searchpath("name", path) -- find a module
coroutine
Treated in Coroutines.
coroutine.create(f)
coroutine.resume(co, ...)
coroutine.yield(...)
coroutine.status(co)
coroutine.wrap(f) -- iterator wrapper
coroutine.running() -- current coroutine (or nil if main)
debug
Introspection and debugging:
debug.traceback("error", level) -- stack trace
debug.getinfo(level, what) -- info about a function
debug.getlocal(level, index) -- local variable
debug.setlocal(level, index, value)
debug.getupvalue(f, index) -- function upvalue
debug.setupvalue(f, index, value)
debug.sethook(hook, mask, count) -- runtime hooks
debug.gethook()
debug.getmetatable(v) -- access raw metatable
debug.setmetatable(v, mt)
debug.debug() -- interactive debugger
The debug admits substantial introspection — conventional in development tools and frameworks; conventionally avoided in production code (admit substantial security risks).
utf8 (Lua 5.3+)
UTF-8-aware string operations:
local s = "héllo"
utf8.len(s) -- 5 (characters; #s would be 6 bytes)
utf8.char(0x41) -- "A"
utf8.codepoint(s, 1) -- code point at byte 1
for p, c in utf8.codes(s) do -- iterator over chars
print(p, c, utf8.char(c))
end
utf8.charpattern -- pattern matching one UTF-8 char
Common patterns
File reading
local function read_all(path)
local f, err = io.open(path, "r")
if not f then return nil, err end
local content = f:read("*a")
f:close()
return content
end
File writing
local function write_all(path, content)
local f, err = io.open(path, "w")
if not f then return nil, err end
f:write(content)
f:close()
return true
end
Date formatting
local now = os.date() -- "Mon Jan 15 10:00:00 2026"
local iso = os.date("%Y-%m-%d %H:%M:%S") -- "2026-01-15 10:00:00"
local utc = os.date("!%Y-%m-%dT%H:%M:%SZ") -- ISO 8601 UTC
Random selection
math.randomseed(os.time())
local arr = {"a", "b", "c", "d", "e"}
local choice = arr[math.random(#arr)]
-- Shuffle:
local function shuffle(arr)
for i = #arr, 2, -1 do
local j = math.random(i)
arr[i], arr[j] = arr[j], arr[i]
end
end
String building
local parts = {}
for i, item in ipairs(items) do
parts[i] = tostring(item)
end
local s = table.concat(parts, ", ")
Sorting with comparator
local people = {
{name = "Alice", age = 30},
{name = "Bob", age = 25},
{name = "Charlie", age = 35}
}
table.sort(people, function(a, b) return a.age < b.age end)
for _, p in ipairs(people) do
print(p.name, p.age)
end
Computing differences
local start = os.clock()
do_substantial_work()
local elapsed = os.clock() - start
print(string.format("took %.3f seconds", elapsed))
Iterating object pairs sorted
local function sorted_pairs(t)
local keys = {}
for k in pairs(t) do keys[#keys + 1] = k end
table.sort(keys)
local i = 0
return function()
i = i + 1
local k = keys[i]
if k ~= nil then return k, t[k] end
end
end
Number formatting
string.format("%d", 42) -- "42"
string.format("%05d", 42) -- "00042"
string.format("%.2f", math.pi) -- "3.14"
string.format("%e", 12345.6789) -- "1.234568e+04"
string.format("%x", 255) -- "ff"
Splitting
local function split(s, sep)
sep = sep or "%s"
local parts = {}
for part in s:gmatch("[^" .. sep .. "]+") do
parts[#parts + 1] = part
end
return parts
end
Joining
local s = table.concat({"a", "b", "c"}, ", ")
Random UUID-like
math.randomseed(os.time())
local function random_id(length)
length = length or 16
local chars = "0123456789abcdef"
local result = {}
for i = 1, length do
local idx = math.random(#chars)
result[i] = chars:sub(idx, idx)
end
return table.concat(result)
end
print(random_id()) -- e.g., "a3f2bc7e890d1234"
For true UUIDs, third-party libraries (e.g., uuid) are conventional.
Reading environment
local home = os.getenv("HOME")
local path = os.getenv("PATH")
local debug = os.getenv("DEBUG") == "1"
Running shell commands
local exit_code = os.execute("ls") -- runs ls; returns exit info
-- For capturing output:
local f = io.popen("ls")
if f then
for line in f:lines() do
print(line)
end
f:close()
end
Checking file existence
local function exists(path)
local f = io.open(path, "r")
if f then f:close(); return true end
return false
end
Computing UTC time
local utc = os.time(os.date("!*t")) -- UTC seconds since epoch
Third-party libraries
For substantial functionality beyond the standard library:
- Penlight — substantial general-purpose extensions (collections, OOP, string utilities, etc.).
- LuaSocket — TCP/UDP networking, HTTP client.
- LuaFileSystem — directory listing, file attributes.
- cjson / dkjson — JSON encoding/decoding.
- LPeg — substantial parsing (PEG-style).
- date — date/time arithmetic.
- luaposix — POSIX functions.
- lua-resty-* (in OpenResty) — HTTP, Redis, MySQL.
- busted — testing framework.
- luacheck — linting.
Install via LuaRocks:
luarocks install penlight
luarocks install dkjson
Common third-party patterns
JSON
local json = require("dkjson")
local data = {name = "Alice", age = 30}
local encoded = json.encode(data) -- '{"name":"Alice","age":30}'
local decoded = json.decode('{"x":1,"y":2}')
print(decoded.x, decoded.y) -- 1 2
HTTP via LuaSocket
local http = require("socket.http")
local body, status = http.request("https://example.com")
if status == 200 then
print(body)
end
Directory listing via LuaFileSystem
local lfs = require("lfs")
for entry in lfs.dir(".") do
if entry ~= "." and entry ~= ".." then
local attrs = lfs.attributes(entry)
print(entry, attrs.mode, attrs.size)
end
end
A note on the conventional discipline
The contemporary Lua standard-library advice:
- Use the standard library for the conventional core operations.
- Use the method-call syntax (
s:upper()) for substantial readability. - Cache
string.format,table.concatetc. as locals in hot loops. - Use
os.date,os.timefor time handling. - Use
io.linesfor substantial file iteration. - Use
math.randomseed(os.time())before substantial random use. - Use LuaRocks libraries for substantial functionality (JSON, HTTP, etc.).
- Use Penlight for substantial substrate extensions.
- Avoid
os.executefor substantial logic; useio.popenfor output. - Use
debug.tracebackwithxpcallfor substantial error logging.
The combination — small core library covering substantial conventional needs, the LuaRocks ecosystem for substantial extensions, the host-application APIs for embedded contexts — is the substance of Lua’s runtime library. The discipline produces concise, expressive code with substantial built-in functionality plus substantial extension via the conventional library ecosystem.