26. Methods and self

You have called functions stored on tables many times — string.format, table.insert, math.floor. This chapter shows how to define your own functions that belong to a particular table, and what the colon syntax (obj:method()) does under the hood. The colon is everywhere in Roblox; understanding it once explains a lot of Roblox code.

Functions on tables, the long way

A function on a table is just a value at a string key:

local counter = { count = 0 }

function counter.step(c)
    c.count = c.count + 1
end

counter.step(counter)
counter.step(counter)
counter.step(counter)
print(counter.count)   -- 3

The function counter.step takes the table itself as its first argument, then uses it to update the counter. Every call has to pass counter explicitly: counter.step(counter). This works but reads awkwardly.

The colon shortcut

Lua's colon does two things at once — a bit of syntax sugar that makes the table-bound calling style cleaner.

Calling with : automatically passes the table as the first argument:

counter:step()   -- exactly the same as counter.step(counter)

Defining with : automatically declares a parameter named self that receives the table:

function counter:step()
    self.count = self.count + 1
end

Same definition as before, written more compactly. Combine both shortcuts:

local counter = { count = 0 }

function counter:step()
    self.count = self.count + 1
end

counter:step()
counter:step()
counter:step()
print(counter.count)   -- 3

Inside the body, self is just a normal parameter name. Write self.x to read a field, self.x = ... to change one. The name self is a convention, not a rule — it is just the implicit parameter the colon definition creates.

Open exercises/26/01-counter.lua. It already has counter:step. Add a counter:reset that sets self.count back to 0, and call it from the bottom of the file.

Built-in colon calls

Many built-in functions accept either dot or colon syntax. The two forms are interchangeable:

local name = "keiko"

print(string.upper(name))   -- KEIKO
print(name:upper())         -- KEIKO     -- same

name:upper() passes name as the first argument to string.upper. This works because strings carry an implicit link to the string table, so any string method can be called as s:method(). You will see this everywhere in real Lua code.

A small game object

Method syntax shines when several pieces of state go together. Here is a dog with a name and a number of barks left:

local dog = {
    name = "Rex",
    barks_left = 3,
}

function dog:bark()
    if self.barks_left <= 0 then
        print(self.name .. " is hoarse.")
        return
    end
    print(self.name .. ": Woof!")
    self.barks_left = self.barks_left - 1
end

dog:bark()       -- Rex: Woof!
dog:bark()       -- Rex: Woof!
dog:bark()       -- Rex: Woof!
dog:bark()       -- Rex is hoarse.

The dog table holds the state; the method changes it on every call. This is the bit of object orientation you need for the next chapter, and the same pattern you see in Roblox: an Instance (a table) with methods like :Destroy(), :GetChildren(), :Clone().

Common mistake: mixing the two forms

obj.method() and obj:method() are not the same call. The dot does not pass obj automatically. If the method was defined with :, calling with . and forgetting to pass obj leaves self as nil:

dog.bark()        -- error: attempt to index a nil value (local 'self')

The fix is one character: use :.

The reverse mistake — obj:method(obj) — passes obj twice. The first goes into self, the second becomes an extra argument. Usually it is just ignored, but it can confuse debugging.

Homework

Problem 1 — Counter object

Open exercises/26/homework/01-counter-object.lua. Build a counter table with the fields and methods:

  • counter.count — starts at 0.
  • counter:inc() — adds 1.
  • counter:get() — returns the current count.
  • counter:reset() — sets count back to 0.

Call them in sequence and print the value at each step.

Problem 2 — Dog with bark

Open exercises/26/homework/02-dog.lua. Build a dog table with a name field and a :bark() method that prints <name>: Woof!. Make a second dog with a different name. Call both :bark() and confirm each prints its own name.

Problem 3 — Calculator object

Open exercises/26/homework/03-calculator.lua. Build a calc table with:

  • calc.value — starts at 0.
  • calc:add(n) — adds n to calc.value.
  • calc:sub(n) — subtracts.
  • calc:mul(n) — multiplies.
  • calc:show() — prints the current value with a label.

Chain a few calls and confirm the value updates as expected.

Challenge — Stack

Open exercises/26/homework/04-stack.lua. Build a stack table that behaves like a stack:

  • stack.items — a list.
  • stack:push(v) — adds v to the top.
  • stack:pop() — removes and returns the top.
  • stack:peek() — returns the top without removing it (or nil if empty).
  • stack:size() — returns how many items are stored.

Use table.insert and table.remove without the second argument — that pushes and pops at the end of the list, the standard stack behaviour.

Stuck or finished? Open the homework solutions page.