luaguides

string.dump

string.dump (function [, strip])

string.dump turns a Lua function into a string of bytes called a binary chunk. You can write that string to a file, send it across a network, or cache it in memory, and later pass it back to load to get a working copy of the function. It is the serialization half of Lua’s compile/serialize loop, and the standard way to ship precompiled bytecode.

The output is not Lua source and is not portable text. It is a version-tagged binary blob that may contain embedded NULs, so treat it as bytes, not characters. When you reload it, the result is a new function value with the same compiled instructions as the original, but with fresh, uninitialized upvalue slots.

Signature and return value

string.dump(function [, strip]) -> string

The function form and the method form are equivalent. The method form is rarely useful for string.dump because the function being dumped is rarely a string, but ("return 1"):dump() works if you ever need it.

ReturnTypeDescription
1stringA binary representation of the function. Pass it to load with mode = "b" (or "bt") to reconstruct the function.

string.dump raises a Lua error when the argument is not a Lua function. C functions, numbers, nil, tables, and threads all fail with a message of the form "unable to dump given function" (the exact wording varies between 5.1, 5.3, and 5.4).

Parameters

#NameTypeDefaultDescription
1functionfunction (Lua function)requiredThe function to serialize. Must be a Lua-authored function, not a C function. Passing anything else raises an error rather than returning nil.
2stripbooleanfalseIf true, the binary chunk is written without debug information. Line numbers, source-file names, and local-variable names are removed. The reloaded function still runs identically; only diagnostics get worse. If false or omitted, the chunk keeps its full debug info.

The strip parameter was added in Lua 5.2. In 5.1 the function takes only one argument. LuaJIT accepts a string of flag characters here instead of a boolean (see the note at the end).

How it works

Lua source goes through three stages: it is parsed into a chunk, the chunk is compiled into instructions plus a constants table, and the compiled form is either run or handed to you. string.dump is the last step in reverse: it takes the compiled form and writes it out as a string. The string starts with a four-byte signature \27Lua plus a version byte (0x54 in Lua 5.4), which is how load decides whether to treat the input as binary or as source text.

Upvalues are not part of the dump. A function that closed over a local variable at dump time still has that upvalue slot in the reloaded copy, but it is empty. If the function tried to read the upvalue at runtime, it would see nil. To carry state through, pass it back in via the env argument to load or wrap the reloaded function in a closure that supplies the missing values.

You can peek at the first few bytes of the returned string to confirm the format:

local function add(a, b) return a + b end
local bytecode = string.dump(add)

print(string.format("%02x %02x %02x %02x",
    bytecode:byte(1), bytecode:byte(2), bytecode:byte(3), bytecode:byte(4)))
-- 1b 4c 75 61  (the four bytes are ESC, 'L', 'u', 'a')

print(string.format("0x%02x", bytecode:byte(5)))
-- 0x54   on Lua 5.4

If those first four bytes do not read as \27Lua, load will fall through and try to parse the string as Lua source instead.

Examples

Round-trip a function

The basic shape: dump, load, call. Each load produces a brand-new function value, so the original and the restored copy are not == equal, but they behave the same.

local function greet(name)
    return "hello, " .. name
end

local bytecode = string.dump(greet)
print(type(bytecode))            -- string
print(#bytecode)                 -- 78 (depends on Lua version and build)

local restored, err = load(bytecode)
print(restored)                  -- function: 0x...  (a new function value)
print(restored("world"))         -- hello, world

If load returns nil, err, the byte string was not a valid binary chunk (corrupt, truncated, or dumped by a different Lua version). Use assert or check the second return value.

Persist a function to disk

io.open in "wb" mode is the way to write the bytes; "rb" is the way to read them back. Text mode truncates at the first NUL byte on Windows, which is the first thing most dumped chunks contain.

-- writer.lua
local function add(a, b) return a + b end
local f = assert(io.open("add.luac", "wb"))
f:write(string.dump(add))
f:close()

-- reader.lua
local f = assert(io.open("add.luac", "rb"))
local chunk = f:read("*a")
f:close()
local add = assert(load(chunk, "add.luac"))
print(add(2, 3))                 -- 5

The chunkname argument ("add.luac" above) shows up in any future error message coming from the reloaded function. Pick something recognizable, and you get cleaner stack traces for free.

Strip debug info for shipping

strip = true removes line numbers and local names. The reloaded function still works; it just stops being useful for post-mortem debugging. The size win is small for short functions and meaningful for large ones with many locals.

local function secret()
    local password = "hunter2"
    return password
end

local with_debug    = string.dump(secret)         -- annotated
local without_debug = string.dump(secret, true)   -- stripped

print(#with_debug, #without_debug)  -- e.g.  86  70

A runtime error inside the stripped version reports [string "?"]:? instead of the line where the error was raised. That is the price of a smaller, harder-to-reverse-engineer chunk.

Dumping a C function errors

C functions have no Lua bytecode to serialize. The call raises rather than returning nil:

print(string.dump(print))
-- stdin:1: unable to dump given function

If you need to persist behavior tied to a C function, store its name instead and look it up on reload. Most embedding hosts provide a registry of C functions for exactly this purpose.

Common pitfalls

  • Cross-version chunks do not load. A chunk dumped by Lua 5.4 cannot be loaded by 5.3 or 5.1, and vice versa. The header byte encodes the version. There is no flag to make it portable; matching interpreter versions are the only option.
  • Endianness and word size matter. A chunk dumped on a 64-bit little-endian build of Lua 5.4 will not load on a 32-bit build of 5.4. Pre-built .luac files shipped to embedded targets need to match the target’s build, or you recompile on the device.
  • C functions are unrepresentable. string.dump(io.write) errors. Persist a name, not a function, when the implementation is on the C side.
  • Upvalues are dropped. The compiled code is preserved, but any closed-over values are not. Wrap the reloaded function in a closure that supplies the missing state, or pass it through env.
  • strip = true kills line numbers. Useful for shipping, hostile to post-mortem debugging. Decide based on whether you or your users need to read traces from the deployed code.
  • Text-mode file I/O truncates the chunk. Use "wb" to write, "rb" to read. This bites on Windows; it is silent on Linux until you dump a function that happens to embed a NUL early.
  • load does not throw on bad input. It returns nil, errmsg. Wrap with assert or check the second value.
  • “Stripped” is not encrypted. Anyone determined can disassemble the bytecode with luac -l, unluac, or chunkspy. string.dump with strip = true is for size and casual obfuscation, not for keeping secrets.

LuaJIT note

LuaJIT’s string.dump accepts a string of flag characters instead of a boolean. "s" strips debug info (same as true in PUC Lua), and "d" produces a deterministic dump, byte-identical for identical source, which is what you want for reproducible builds. PUC Lua will accept a string here too, but it treats any truthy value as strip = true, so "sd" ends up stripped without the determinism guarantee.

-- LuaJIT only
local f = function(x) return x * 2 end

local stripped     = string.dump(f, "s")    -- no debug info
local reproducible = string.dump(f, "sd")   -- stripped, byte-stable across runs

The bytecode format is its own thing, so a LuaJIT chunk will not load under PUC Lua and vice versa.

See also

  • load — the inverse of string.dump. Use mode = "b" to refuse source and accept only precompiled chunks.
  • loadfile — file-reading sibling of load, useful for caching .luac files.
  • require — built on loadfile; the standard module loader uses the same compiled-chunk machinery internally.
  • Lua serialization guide — broader treatment of persistence, including string.dump for functions and string.format with %q for source-text round-trips.