Метатаблицы и __index: ООП на Lua

Узнаём, как из обычных таблиц сделать объекты с методами и наследованием.

Метатаблица — это таблица, которая описывает «особое поведение» другой таблицы: что делать при сложении, сравнении или обращении к отсутствующему ключу.

В Lua нет классов как отдельной конструкции. Зато есть метатаблицы — механизм, на котором строят и ООП, и операторы, и многое другое. Это самая «магическая» часть языка.

Метаметод __index

Когда вы обращаетесь к отсутствующему полю таблицы, Lua заглядывает в её метатаблицу, в поле __index. Это и есть основа наследования:

local Animal = {}
Animal.__index = Animal

function Animal.new(name)
  local self = setmetatable({}, Animal)
  self.name = name
  return self
end

function Animal:speak()
  print(self.name .. " издаёт звук")
end

local cat = Animal.new("Кот")
cat:speak()

Вывод:

Кот издаёт звук

Здесь setmetatable({}, Animal) связывает новый объект с таблицей Animal. Когда мы зовём cat:speak(), Lua не находит speak в cat и через __index берёт его из Animal.

Двоеточие и self

Запись function Animal:speak() с двоеточием автоматически добавляет первый скрытый параметр self — ссылку на объект. Вызов cat:speak() тоже через двоеточие передаёт cat как self.

Наследование

local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog

function Dog.new(name)
  local self = Animal.new(name)
  return setmetatable(self, Dog)
end

function Dog:speak()
  print(self.name .. " говорит: Гав!")
end

local rex = Dog.new("Рекс")
rex:speak()

Вывод:

Рекс говорит: Гав!

Цепочка поиска

cat.speak  не найдено в cat
   │
   └─> __index = Animal
         │
         └─> Animal.speak  найдено!

Как работает под капотом

Метатаблицы дают и другие метаметоды: __add для оператора +, __eq для ==, __tostring для красивого вывода через print. Например, задав __add, можно складывать векторы как обычные числа. Именно так в Roblox устроены типы Vector3.

Частые ошибки

  • Забыть строку Animal.__index = Animal — тогда методы не найдутся и будет ошибка про nil.
  • Путать . и : при вызове методов. Метод с self зовут через двоеточие.
  • Думать, что __index копирует методы в объект. Он лишь указывает, где их искать.

Итог

  • Метатаблица задаёт особое поведение таблицы через метаметоды.
  • __index описывает, где искать отсутствующие поля — это основа наследования.
  • setmetatable связывает объект с «классом»; двоеточие добавляет скрытый self.
  • Другие метаметоды (__add, __eq, __tostring) переопределяют операторы и вывод.
Проверьте себя
1. Для чего служит метаметод __index в метатаблице?
AДля нумерации элементов
BЧтобы указать, где искать отсутствующие в таблице поля — основа наследования
CДля сортировки таблицы
DДля удаления таблицы
2. Что добавляет двоеточие в записи function Obj:method()?
AНичего, это то же что и точка
BСкрытый первый параметр self — ссылку на объект
CВозврат значения
DГлобальную область видимости