Skip to main content

Variables, Scopes & Environments

Variable types

KindScopeDefault
GlobalEntire programnil if unset
LocalLexical blockDeclared with local
UpvalueCaptured by closureOuter local variable

Global variables

Any variable not declared local is global. Globals live in the environment table _ENV.

x = 10 -- global: same as _ENV.x = 10
print(x) -- 10

Free names → _ENV

Every chunk gets an implicit local _ENV = <environment>. Any free name var is sugar for _ENV.var.

-- These are equivalent:
print("hi")
_ENV.print("hi")

[!important] _ENV is a local _ENV is a regular local variable. You can reassign it to sandbox code:

local _ENV = {print = print, pairs = pairs}
print("sandboxed!") -- works
os.execute("rm -rf /") -- error: os is nil

Local variables

Declared with local keyword. Scope extends from declaration to end of enclosing block.

do
local x = 10
print(x) -- 10
end
print(x) -- nil (out of scope, or global x if defined)

Scope rules

  • Scope starts after the declaration
  • Idiomatic to declare locals as close to use as possible
  • local in inner block shadows outer variable
local x = 10
do
local x = 20 -- shadows outer x
print(x) -- 20
end
print(x) -- 10

Declaration forms

local a -- initialized to nil
local b = 1 -- single value
local c, d = 1, 2 -- multiple assignment
local e, f = 1, 2, 3 -- extra values discarded
local g, h = 1 -- h = nil (missing values)

Assignment

Single assignment

x = 10
t.name = "Lua"

Multiple assignment

a, b = 10, 20
a, b = b, a -- swap!
a, b, c = 1, 2 -- c = nil
a, b = 1, 2, 3 -- 3 discarded

[!tip] Multiple assignment evaluates all right-side expressions first, then assigns left-to-right.

a, b = f() -- f() can return multiple values

Environments & _G

The global environment is the default table for _ENV. It's accessible as _G:

print(_G == _ENV) -- true (in main chunk)

Sandbox example

local safe_env = {
print = print,
pairs = pairs,
ipairs = ipairs,
type = type,
tostring = tostring,
tonumber = tonumber,
math = math,
string = string,
table = table,
}

local f = load("return math.sqrt(16)", "sandbox", "t", safe_env)
print(f()) -- 4.0

_ENV and modules

The standard module pattern manipulates _ENV:

local M = {}
local _ENV = M -- all free names now refer to M

function add(a, b)
return a + b
end

return M

Scoping diagram

┌─ chunk (global scope) ──────────────────┐
│ _ENV = _G │
│ global_var = ... │
│ │
│ ┌─ function f() ──────────────────┐ │
│ │ local upval = ... (upvalue) │ │
│ │ │ │
│ │ ┌─ do block ──────────────┐ │ │
│ │ │ local inner = ... │ │ │
│ │ │ -- can see: upval, │ │ │
│ │ │ -- inner, _ENV │ │ │
│ │ └─────────────────────────┘ │ │
│ │ -- inner is out of scope │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘