0Pricing
Lua Academy · Lesson

__tostring and __len

Control string representation and the # operator via metamethods.

__tostring and __len is a free Lua Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

__tostring Metamethod

__tostring is called when tostring(obj) is invoked, and also by print() (which calls tostring on each argument). Define it to provide a human-readable representation of your custom objects.

local Point = {}
Point.__index = Point
Point.__tostring = function(p)
  return string.format("Point(%.2f, %.2f)", p.x, p.y)
end

function Point.new(x,y)
  return setmetatable({x=x,y=y}, Point)
end

local p = Point.new(3.14, 2.72)
print(p)            -- Point(3.14, 2.72)
print(tostring(p))  -- Point(3.14, 2.72)

__len for Sequences

__len is called when the # operator is applied to a table with this metamethod. In Lua 5.2+, __len is always called for tables; in 5.1 it's only called for userdata. Use it to define "length" for custom containers.

local Queue = {}
Queue.__index = Queue
Queue.__len = function(q) return q._tail - q._head + 1 end

function Queue.new()
  return setmetatable({_data={},_head=1,_tail=0}, Queue)
end
function Queue:push(v)
  self._tail = self._tail + 1
  self._data[self._tail] = v
end
function Queue:pop()
  if self._head > self._tail then return nil end
  local v = self._data[self._head]
  self._head = self._head + 1
  return v
end

local q = Queue.new()
q:push("a"); q:push("b"); q:push("c")
print(#q)   -- 3

Rich __tostring for Debugging

A good __tostring includes the type name and key fields. For nested objects, call tostring recursively. This makes debugging much easier than the default "table: 0x..." output.

local function tableToString(t, depth)
  depth = depth or 0
  if depth > 3 then return "{...}" end
  local parts = {}
  for k, v in pairs(t) do
    if type(v) == "table" then
      parts[#parts+1] = k.."="..tableToString(v, depth+1)
    else
      parts[#parts+1] = k.."="..tostring(v)
    end
  end
  return "{"..table.concat(parts,", ").."}"
end

local obj = {name="test", data={x=1,y=2}, active=true}
print(tableToString(obj))

__len vs # Operator

For tables without a __len metamethod, #t finds any border of the sequence (where t[i] ~= nil and t[i+1] == nil). With holes in the table, the result is unpredictable. __len lets you define an authoritative, consistent length.

-- Table with hole: # is unpredictable
local t = {10, 20, nil, 40}
print(#t)  -- could be 2 or 4, undefined!

-- Custom container with authoritative length
local SafeList = {}
SafeList.__index = SafeList
SafeList.__len = function(s) return s._size end

function SafeList.new()
  return setmetatable({_data={}, _size=0}, SafeList)
end
function SafeList:add(v)
  self._size = self._size + 1
  self._data[self._size] = v
end
local s = SafeList.new()
s:add(1); s:add(nil); s:add(3)
print(#s)   -- 3 (always correct)

__tostring in Error Messages

When you use error objects (tables instead of strings), having __tostring defined makes error messages readable when the error propagates and gets converted to a string.

local function makeError(code, msg)
  return setmetatable(
    {code=code, message=msg},
    {__tostring = function(e)
      return string.format("Error[%d]: %s", e.code, e.message)
    end}
  )
end

local ok, err = pcall(function()
  error(makeError(404, "resource not found"))
end)

if not ok then
  -- err is the error object (table)
  print(tostring(err))  -- may show table address...

__tostring with Inheritance

When using prototype-based OOP, define __tostring on the class metatable. All instances automatically use the class's __tostring because the instance's metatable IS the class table.

local Animal = {}
Animal.__index = Animal
Animal.__tostring = function(a)
  return string.format("%s(%s)", a._type or "Animal", a.name or "?")
end

function Animal.new(name, atype)
  return setmetatable({name=name, _type=atype}, Animal)
end

local cat = Animal.new("Whiskers", "Cat")
local dog = Animal.new("Rex", "Dog")
print(cat)  -- Cat(Whiskers)
print(dog)  -- Dog(Rex)

Implementing a Set with __len and __tostring

Combine __len and __tostring in one type for a complete, user-friendly Set implementation.

local Set = {}
Set.__index = Set
Set.__len = function(s) return s._size end
Set.__tostring = function(s)
  local items = {}
  for k in pairs(s._data) do items[#items+1]=tostring(k) end
  table.sort(items)
  return "Set{" .. table.concat(items,", ") .. "}"
end

function Set.new(...)
  local obj = setmetatable({_data={},_size=0}, Set)
  for _, v in ipairs({...}) do
    if not obj._data[v] then obj._data[v]=true; obj._size=obj._size+1 end
  end
  return obj
end

local s = Set.new(3,1,4,1,5,9,2,6)
print(#s)         -- 7 (duplicates removed)
print(tostring(s)) -- Set{1, 2, 3, 4, 5, 6, 9}

Numeric Objects with Both

Create a fraction type with __tostring for display and __len as digit count (or a domain-specific metric).

local Frac = {}
Frac.__index = Frac

local function gcd(a,b) return b==0 and a or gcd(b,a%b) end

function Frac.new(n,d)
  local g = gcd(math.abs(n), math.abs(d))
  return setmetatable({n=n//g, d=d//g}, Frac)
end

Frac.__tostring = function(f)
  if f.d==1 then return tostring(f.n) end
  return f.n .. "/" .. f.d
end

Frac.__add = function(a,b)
  return Frac.new(a.n*b.d + b.n*a.d, a.d*b.d)
end

local a = Frac.new(1,2)
local b = Frac.new(1,3)
print(a + b)   -- 5/6

Overriding Default print Behavior

Lua's print calls tostring on each argument separated by tabs. With __tostring defined, your objects display meaningfully in any print statement, error message, or string interpolation that uses tostring.

local Color = {}
Color.__index = Color
Color.__tostring = function(c)
  return string.format("#%02X%02X%02X", c.r, c.g, c.b)
end

function Color.new(r,g,b)
  return setmetatable({r=r,g=g,b=b}, Color)
end

local red   = Color.new(255,0,0)
local green = Color.new(0,255,0)
local blue  = Color.new(0,0,255)

print(red, green, blue)
-- #FF0000  #00FF00  #0000FF

__name in Lua 5.3+

Lua 5.3+ supports a __name field in the metatable which is used by the default error messages to identify the type. While it doesn't affect tostring() directly, it appears in runtime error messages when you perform invalid operations on the type.

local MyType = {}
MyType.__index = MyType
MyType.__name = "MyType"   -- used in error messages
MyType.__tostring = function(t)
  return "MyType(" .. tostring(t.value) .. ")"
end

local obj = setmetatable({value=42}, MyType)
print(obj)             -- MyType(42)

-- Error message will say "MyType" instead of "table"
-- pcall(function() local x = obj + 1 end)

Quick Check

When is __tostring called?

Recap: __tostring and __len

Summary:

  • __tostring: called by tostring() and print()
  • __len: called by #obj; provides authoritative length for custom containers
  • Define __tostring on class tables for all instances
  • Use __len when the raw table structure doesn't give correct #
  • __name (5.3+) names the type in error messages

Frequently asked questions

Is the “__tostring and __len” lesson free?

Yes — the full text of “__tostring and __len” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.

What will I learn in “__tostring and __len”?

Control string representation and the # operator via metamethods. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Lua Academy?

No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “__tostring and __len” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Lua Academy lesson?

Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Introduction to Metatables
  2. __index and __newindex
  3. Arithmetic Metamethods
  4. __tostring and __len
← Back to Lua Academy