string.char
string.char (...) Synopsis
string.char (···)
string.char takes zero or more integers and returns a string whose bytes
are those integers in order. The result has exactly one byte per argument,
so #string.char(a, b, c) == 3. With no arguments it returns the empty
string "". It is the standard inverse of string.byte:
round-tripping a sequence through byte and then char gives you back
the same string.
If you are coming from C, the mental model is “build a char[] from
byte values.” If you are coming from Python, the closest equivalent is
bytes([...]), with the same caveat that string.char is byte-oriented,
not Unicode-aware.
Quick look
print(string.char(72, 101, 108, 108, 111)) --> Hello
print(string.char(65, 66, 67)) --> ABC
print(#string.char()) --> 0
The first call produces the word Hello from five byte codes. The
second packs A, B, C into a single string. The third confirms
that an empty call yields the empty string of length 0.
Parameters
string.char is variadic and the manual does not name the arguments.
Treat them as a positional list of integer codes.
| # | Name | Type | Default | Description |
|---|---|---|---|---|
| 1..N | (positional) | integer | none | Internal numeric code of one byte. Must lie in [0, 255]. The byte appears at the same position in the result string. Floats with an integral value (for example 65.0) are coerced to the integer 65; floats with a non-integral value are rejected. |
The valid range is [0, 255] on every 8-bit-clean Lua implementation,
including the reference PUC-Rio build, LuaJIT, and Luau. The Lua 5.4
manual still warns that “numeric codes are not necessarily portable
across platforms” — defensible advice for code that has to run on
exotic or non-standard hosts, but in practice the [0, 255] range has
held since Lua 5.0.
Return Value
string.char returns a string whose length equals select('#', ...):
each argument produces exactly one byte, in argument order. The output
is a raw byte sequence, not characters in any particular encoding —
string.char does not decode or interpret the bytes, it just packs
them. Lua strings are immutable, so the call always returns a fresh
string; there is no in-place variant.
The length claim is the one most worth checking in your own code, because it is what trips up “is the string empty?” checks downstream:
print(#string.char()) --> 0
print(#string.char(72, 105)) --> 2
print(#string.char(65, 66, 67)) --> 3
Examples
Building a known string from byte codes
The most common use is reconstructing an ASCII string from a known
sequence of codes. The codes 65, 66, and 67 happen to be
uppercase A, B, and C in ASCII, which makes them a friendly
example. Once you know the codes, the construction is mechanical:
pass them in order and string.char returns the assembled string,
with one byte per argument. The length operator # then returns the
argument count, which is also the byte count of the result.
print(string.char(65, 66, 67)) --> ABC
print(#string.char(65, 66, 67)) --> 3
The first call packs bytes 65, 66, 67 (the codes for A, B, C) into
a string and prints it. The second confirms the length is 3, one byte
per argument.
Round-tripping with string.byte
string.char and string.byte are inverses. This is useful for
sanity-checking byte sequences coming back from a parser or a binary
protocol.
local s = string.char(72, 101, 108, 108, 111) -- "Hello"
print(s) --> Hello
print(string.byte(s, 1, #s)) --> 72 101 108 108 111
The middle line is the human-readable string. The last line reads every byte back out as its numeric code. If you ever see the codes come back in a different order, something in the protocol layer is swapping endianness or dropping bytes.
Empty call and embedded NUL
Two edges worth pinning down: the no-argument call, and the fact that zero bytes are valid bytes.
print(#string.char()) --> 0
print(#string.char(72, 0, 105)) --> 3
print(string.format("%q", string.char(72, 0, 105)))
--> "H\0i"
string.char() with no arguments is the empty string. string.char(72, 0, 105)
is the three-byte sequence H, NUL, i — length 3, not 1. A bare
print on a string containing NUL just stops at the NUL when writing
to a C stream, so string.format("%q", ...) is the cleanest way to see
the full content in the REPL. Lua strings are not null-terminated, so
this matters in practice any time you handle binary data; see the
binary data guide for a deeper look.
Out-of-range input
Passing a value outside [0, 255] is a hard error.
print(pcall(string.char, -1)) --> false value out of range
print(pcall(string.char, 256)) --> false value out of range
print(pcall(string.char, 65.5)) --> false number has no integer representation
Wrap untrusted input in pcall if you do not control the source. The
error message comes from luaL_argcheck in lstrlib.c, so the exact
text is "value out of range" for integer out-of-range, and the
standard coercion error for non-integral floats.
Common Pitfalls
string.char is small and well-specified, but the asymmetries with
related functions catch people out. The cases below are the ones
worth a defensive comment in your own code.
-
string.charis a static library function. The expression"A":char(65)is a runtime error because there is nostring:charmethod. By contrast,string.bytedoes have a method form (s:byte(i, j)), and the asymmetry catches people regularly. -
string.charonly knows about 8-bit bytes. It will not turn a Unicode codepoint into the right UTF-8 for you. For codepoint-level work in Lua 5.3+, preferutf8.charfrom theutf8standard library, which expects a single codepoint and returns the proper UTF-8 encoding. The two snippets below produce the same three-byte UTF-8 sequence forU+2603(the snowman character):-- Hand-built UTF-8 bytes for U+2603 (snowman) using string.char print(string.format("%q", string.char(0xE2, 0x98, 0x83))) --> "\226\152\131" -- Same codepoint via the stdlib utf8 helper print(string.format("%q", utf8.char(0x2603))) --> "\226\152\131"utf8.charis less error-prone on supplementary planes;string.charis fine when you already have bytes in hand. -
Values outside
[0, 255]are an error rather than a silent clamp. If you are reading bytes off the wire and one happens to be256because of a parser bug, you want to hear about it, not silently get a truncated string. -
Float handling is half-strict.
string.char(65.0)returns"A"because Lua 5.4 coerces integral floats, butstring.char(65.5)errors with “number has no integer representation.” The coercion is automatic, not optional:print(string.char(65.0)) --> A print(pcall(string.char, 65.5)) --> false number has no integer representation -
string.format("%c", n)is not a drop-in replacement forstring.char. It also produces"A"from65, but it only handles a single character per call and is noticeably slower for building byte sequences. Usestring.charfor the multi-byte case:-- Slow: one format call per byte, multiple intermediate strings local slow = string.format("%c%c%c", 72, 105, 33) -- "Hi!" -- Fast: one char call, one allocation local fast = string.char(72, 105, 33) -- "Hi!" -
Very large argument lists allocate one big string. Nothing in the spec caps the argument count, but
string.charbuilds a single contiguous string, so feeding it millions of bytes at once can pressure the allocator. Build in chunks if you have a hard upper bound on the result size.
See Also
string.byte— the direct inverse. Reads bytes out of a string as integers.- Lua strings and patterns — the beginner-friendly introduction to the byte-vs-character model that this function relies on.
- Binary data in Lua —
string.charis the standard tool for building byte blobs; this guide shows the full pattern for reading and writing binary records. - String patterns — patterns operate on
raw strings, the same level
string.charproduces.