luaguides

tostring

tostring(v)

tostring converts any Lua value into a human-readable string. Numbers, booleans, and nil all convert predictably. Tables, functions, threads, and userdata get a pointer address like table: 0x7f8a3c102030 — the object’s memory location in the C implementation. The original value is never modified.

Signature

tostring(v)

Parameters:

  • v — any Lua value

Returns: string

The function always succeeds — it never errors regardless of the input type. Even nil converts cleanly to the string "nil". This reliability makes tostring safe to use in logging and error-handling code where you cannot predict what type a variable will hold at runtime. The sections below walk through how each category of Lua value behaves under conversion.

Basic Types

tostring(42)        --> "42"
tostring(3.14)      --> "3.14"
tostring(-100)       --> "-100"
tostring(true)       --> "true"
tostring(false)      --> "false"
tostring(nil)        --> "nil"

Numbers are formatted as you’d write them in source: no rounding, no trailing zeros for integers. Booleans become the strings "true" and "false", while nil produces the literal string "nil" — distinct from an actual nil value. These basic types all have a natural, unambiguous text form.

For complex types, however, there is no single obvious representation. A table could hold arbitrary key-value pairs with no canonical rendering, so tostring falls back to showing the type name followed by a memory address. This is enough to distinguish objects — two different tables will always produce different addresses — but it tells you nothing about what the table contains. For functions, threads, and userdata, the same rule applies: the address is the only identifying information available without a custom metamethod.

Complex Types

tostring({})           --> "table: 0x7f8a3c102030"
tostring(print)        --> "function: 0x7f8a3c102100"
tostring(coroutine.create(function() end))
                      --> "thread: 0x7f8a3c102200"

The hex address is the object’s memory location. Two identical-looking tables always produce different addresses because they are distinct objects allocated at different points in memory. Keep in mind that these addresses are implementation-specific — LuaJIT and PUC Lua may format them differently, and the values change with every program run.

While the address-only output limits tostring for debugging complex structures, the function’s real strength lies elsewhere. Its most common use is enabling safe string building: Lua’s concatenation operator .. demands strings on both sides, so any number or boolean must be converted first.

Why you need it

Lua’s .. concatenation operator requires strings on both sides. Without tostring, converting a number or boolean for concatenation raises an error:

"score: " .. 42         --> error: attempt to concatenate string with number
"score: " .. tostring(42)  --> "score: 42"

This is the main reason tostring exists — enabling string building with non-string values. Without it, you would need to manually check each value’s type and convert it with specialised branches, which adds boilerplate and risk. The built-in function handles all the type-specific logic internally, giving you a single, predictable interface for any kind of value.

The practical patterns below show how this single-function approach simplifies several common programming tasks. Each pattern builds on the same core idea: convert anything to a string first, then work with the string result.

Practical Patterns

Debug Logging

function dump(value)
  print(tostring(value))
end

dump(42)        --> 42
dump({a = 1})   --> table: 0x...
dump(nil)       --> nil

The dump function works well for quick inspections, but the table output is nearly useless for debugging actual data structures — a table with a dozen keys prints the same opaque address as an empty one. For meaningful table inspection, you need a recursive pretty-printer or a serialization library that walks the table’s contents. In practice, though, tostring is more often used for safe string building than for debugging.

The next pattern shows how wrapping values in tostring lets you concatenate strings with any type without writing type-specific branches. This is the bread-and-butter use case: building log messages, user-facing labels, and error strings from values whose types you may not know ahead of time.

Safe Concatenation

function label(key, value)
  return key .. ":" .. tostring(value)
end

label("count", 5)     --> "count:5"
label("name", nil)   --> "name:nil"
label("active", true) --> "active:true"

The label function demonstrates uniform string building regardless of input type — numbers, booleans, and nil all produce sensible output through the same code path. This pattern is especially useful in logging utilities and data export functions where you might not control what types arrive at runtime.

You can extend this technique to handle default values. Since tostring always returns a string, you can safely check the result for edge cases like empty inputs or nil, and substitute a fallback when needed. The function below shows the approach: convert first, then inspect.

Default Values

function greet(name)
  name = tostring(name)
  if name == "" or name == "nil" then
    return "Hello, World"
  end
  return "Hello, " .. name
end

greet("Alice")   --> "Hello, Alice"
greet(nil)       --> "Hello, nil"
greet("")        --> "Hello, World"

tostring vs string concatenation

Lua’s string concatenation operator .. has a subtle behaviour worth understanding. In Lua 5.3 and later, writing "" .. 42 works because the left operand is already a string, and Lua coerces the right operand automatically by calling the numeric-to-string conversion internally. This implicit coercion happens only when the left side provides a clear string context — it is not a general-purpose tostring replacement.

The asymmetry matters. If you reverse the operands and write 42 .. "", Lua sees a number on the left first, and the .. operator cannot start from a number. The parser rejects it before any conversion logic runs. Explicit tostring avoids this ordering trap entirely and makes the intent clear to anyone reading the code.

"" .. 42          --> "42" (works)
tostring(42) .. "" --> "42" (explicit and clear)

__tostring Metamethod

The __tostring metamethod gives tables a way to define their own string representation, replacing the default table: 0x... output with something meaningful. When you set a __tostring function on a table’s metatable, Lua calls it whenever tostring is invoked on that table. This lets you build readable debug output for custom data structures — for example, a 2D point can display as (1, 2) instead of an opaque address.

local point = setmetatable({x = 1, y = 2}, {
  __tostring = function(self)
    return string.format("(%d, %d)", self.x, self.y)
  end
})
print(tostring(point))   --> "(1, 2)"

Note: tostring checks the metatable for __tostring and calls it when present. This works in Lua 5.1 through 5.4 and LuaJIT. If __tostring is not defined on a table, you get the default table: 0x... address string. The metamethod receives the table as its first argument and must return a string — returning anything else causes an error.

See Also