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.
| Return | Type | Description |
|---|---|---|
| 1 | string | A 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
| # | Name | Type | Default | Description |
|---|---|---|---|---|
| 1 | function | function (Lua function) | required | The function to serialize. Must be a Lua-authored function, not a C function. Passing anything else raises an error rather than returning nil. |
| 2 | strip | boolean | false | If 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
.luacfiles 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 = truekills 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. loaddoes not throw on bad input. It returnsnil, errmsg. Wrap withassertor check the second value.- “Stripped” is not encrypted. Anyone determined can disassemble the bytecode with
luac -l,unluac, orchunkspy.string.dumpwithstrip = trueis 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 ofstring.dump. Usemode = "b"to refuse source and accept only precompiled chunks.loadfile— file-reading sibling ofload, useful for caching.luacfiles.require— built onloadfile; the standard module loader uses the same compiled-chunk machinery internally.- Lua serialization guide — broader treatment of persistence, including
string.dumpfor functions andstring.formatwith%qfor source-text round-trips.