luaguides

load

load(chunk [, chunkname [, mode [, env]]])

load is the compiler in Lua’s basic-function toolkit. You hand it a chunk (as a string or a reader function), and it returns the compiled chunk as a function, but doesn’t run it. The single most common mistake newcomers make with load is treating that returned function as if it were the chunk’s result; it isn’t, it’s the chunk itself. Call it, and the code runs.

This is also why load is the building block under require, loadfile, dofile, and most plugin or sandbox systems. Anything that “evaluates Lua text at runtime” is a thin wrapper around load (plus a call to the result).

Syntax

load(chunk [, chunkname [, mode [, env]]])

load is a basic function defined in the Lua 5.4 reference manual (§6.1). All four parameters are documented below.

Parameters

#NameTypeDefaultDescription
1chunkstring or functionrequiredSource to compile. A string is the literal chunk text. A function is called repeatedly; each call returns a string that is concatenated to previous results, and returning "" ends the source. The function form is the “reader function” pattern for streaming or incrementally built sources.
2chunknamestring"chunk" (string case), "=??" (function case)A label used only in error messages and debug info. It becomes the source name reported by debug.info and shown in error messages. Pass something meaningful (a filename, a request id), and debugging gets a lot easier.
3modestring"bt"One of "b", "t", or "bt". "b" accepts only binary (precompiled) chunks produced by string.dump. "t" accepts only text. "bt" (the default) auto-detects. Use "b" or "t" when you want to lock down what load will accept.
4envtable (or any value usable as _ENV)the global environment (_G)The value assigned to the chunk’s _ENV upvalue. Pass a curated table to sandbox the chunk: every global lookup inside the loaded code resolves against this table instead of _G.

Return values

On success, load returns a single value: the compiled chunk as a function. That function has exactly one upvalue, _ENV, which is initialized to env if you supplied one, or to the global environment otherwise.

On failure, load returns two values: nil and a string describing the syntax error. Syntax errors do not raise; they return. That’s why almost every example wraps the call in assert, which converts the nil into a thrown error:

local f = assert(load(maybe_bad_source))
f()  -- run it

The two-return form is what you reach for when you want to handle a bad chunk without crashing the whole program. Log the error, retry from a different source, fall back to a default. The chunk’s compile failure looks like any other Lua error, so the usual error-handling patterns apply:

local f, err = load(maybe_bad_source)
if not f then
  -- log, retry, fall back (your call)
end

The only thing that does raise from load is an out-of-memory error, which behaves like any other allocation failure.

Worked examples

Example 1: Compile a string, then call it

The minimum useful load:

local src = "return 1 + 2"
local f = assert(load(src, "expr", "t"))
print(f())  -- 3

src is text. load compiles it (treating it as a text chunk, labeled "expr" for any future error message), and f() runs it and returns the value of the expression, which is 3.

Example 2: Dynamic function generation

Here’s where load starts to earn its keep: building a fresh closure whose body is data-driven.

local function make_adder(n)
  local src = string.format("return function(x) return x + %d end", n)
  return assert(load(src, "make_adder:" .. n, "t"))()
end

local add5  = make_adder(5)
local add12 = make_adder(12)
print(add5(10))   -- 15
print(add12(10))  -- 22

Each call to make_adder produces a brand-new function with a captured n baked into its body. The chunkname includes n, so if a future runtime error comes from add12, the traceback will tell you it came from make_adder:12.

Example 3: Sandboxing untrusted code

Pass a custom env to limit what the chunk can reach:

local safe_env = setmetatable({
  print  = print,
  safe_add = function(a, b) return a + b end,
}, { __index = function(_, k)
  error("access to global '" .. tostring(k) .. "' is denied", 2)
end})

local user_code = [[
  print("starting")
  return safe_add(1, 2)
]]

local f, err = load(user_code, "user", "t", safe_env)
if f then
  print(f())              -- starting
                         -- 3
else
  print("compile:", err)  -- syntax error path
end

Inside the chunk, _ENV is safe_env. print and safe_add resolve. Any attempt to touch os, io, string, or any other global trips the __index metamethod and raises a clear denial. The companion guide on Lua sandboxing goes deeper on this pattern.

Example 4: Stream from a reader function

When the source isn’t a single string (it comes in pieces, or you want to avoid building a giant concatenation up front), pass a function instead. The function is called repeatedly until it returns "":

local i = 0
local function reader()
  i = i + 1
  if i > 3 then return "" end
  return "print('chunk line " .. i .. "')\n"
end

local f = assert(load(reader, "streamed", "t"))
f()
-- output:
-- chunk line 1
-- chunk line 2
-- chunk line 3

Example 5: Round-trip a function with binary mode

mode = "b" only accepts precompiled chunks, which is what string.dump produces. Use it when you want to ship serialized functions or cache parsed source:

local original = function(x) return x * 2 end
local binary   = string.dump(original)            -- serialize to bytes
local restored = assert(load(binary, "restored", "b"))
print(restored(21))                                -- 42

Common gotchas

load is not run. A returned function does nothing until you call it. This is the single most-asked Lua question online.

String chunks get an implicit return. When chunk is a string, load prepends "return " to it before compiling. So load("a = 1") is equivalent to load("return a = 1"), which is a syntax error. For side effects on load, wrap in a do … end block, or write the expression form: load("return do_something()").

chunkname only affects diagnostics. It does not change behavior, the file system, or the environment. Its only job is making error messages and debug.info output useful.

env = nil is a programming error. _ENV must be a table (or table-like value) for the chunk to compile into something you can actually run. If you want the default environment, omit the argument.

loadstring is gone. In Lua 5.1 the string form had its own function, loadstring(s [, name]). It was removed in 5.2. In 5.4 you use load for both string and function sources.

The reader function returns "" to terminate. Forget the empty-string sentinel and load will keep calling forever (or until it hits a memory limit).

FunctionReads fromRuns it?Use case
loadstring or reader functionnoDynamic code generation, plugins, sandboxes
loadfilefile by namenoCustom require-like loaders
dofilefile by nameyesQuick scripts
requiremodule search pathyes, cachedStandard module loading (built on loadfile)
string.dumpa functionSerializes a function to a binary chunk that load can read back

load is the lowest-level of the bunch and the only one with full control over chunkname, mode, and env.

Conclusion

Reach for load when you need to compile Lua source you don’t have at author time: user-supplied scripts, dynamically built functions, plugin bodies, or anything you’d otherwise evaluate with a code-generation hack. For loading modules from disk in the normal way, require is the right tool: load is what require is built on, and it shows you the seams.

See also

  • assert: the canonical wrapper around load
  • pcall: for handling runtime errors from the loaded chunk
  • xpcall: protected loading with a custom message handler
  • error: what a failed load looks like to the caller
  • type: check type(chunk) == "function" before treating it as a reader
  • require: the higher-level cousin; reach for it unless you need load’s flexibility
  • Sandboxing in Lua: full patterns for restricting chunk access