Язык Lua: синтаксис, таблицы и где применяется

Язык Lua: синтаксис, таблицы и где применяется Полезное

Lua — легкий встраиваемый скриптовый язык программирования: маленькое ядро на C, простой синтаксис и одна универсальная структура данных — таблица. Его редко берут как единственный язык проекта; чаще Lua встраивают внутрь программы на C или C++, чтобы описывать на нем логику, конфигурацию и сценарии. Ниже разберем, как устроен синтаксис, почему таблица заменяет и массив, и словарь, что означает «компилятор» применительно к Lua, и где язык реально работает — от игр до nginx и Redis.

Актуальная стабильная ветка на сентябрь 2026 — Lua 5.4 (внутри выходят точечные обновления 5.4.x с исправлениями). Все примеры ниже рассчитаны на нее.

Интерпретируемый язык, байткод и luac: что здесь «компилятор»

Слово «компилятор» в старом названии статьи вводит в заблуждение, поэтому разведем термины сразу.

  • Компилируемый язык (например, C) целиком переводится в машинный код процессора отдельной программой-компилятором, и уже готовый бинарник запускает операционная система.
  • Интерпретируемый язык исполняется программой-интерпретатором, а не напрямую процессором.

Lua относится ко второму типу, но с нюансом. Интерпретатор не читает исходный текст построчно каждый раз: при загрузке он сначала транслирует код в байткод (компактные инструкции), а затем этот байткод исполняет виртуальная машина Lua — программный «процессор» со своими регистрами. То есть внутренняя компиляция в байткод есть, а компиляции в машинный код платформы — нет.

Отдельно поставляется утилита luac — это прекомпилятор: он заранее превращает .lua-файл в файл байткода, но снова не в машинный код. Запускать такой файл все равно будет виртуальная машина Lua.

# прекомпиляция исходника в байткод (не в машинный код платформы)
luac -o hello.luac hello.lua
# запуск байткода тем же интерпретатором
lua hello.luac

Вывод в консоли будет таким же, как при запуске lua hello.lua. Важно: файл hello.luac не самостоятельный исполняемый файл, для него по-прежнему нужен интерпретатор lua.

Первая рабочая программа

Начнем с минимального полного примера, который можно сохранить в файл hello.lua и запустить командой lua hello.lua.

-- hello.lua
local name = "Otus"
local version = _VERSION      -- глобальная строка вида "Lua 5.4"
print("Привет, " .. name)
print("Версия: " .. version)

Вывод в консоль:

Привет, Otus
Версия: Lua 5.4

Разберем по строкам. Комментарий начинается с --. Ключевое слово local объявляет локальную переменную (без него имя стало бы глобальным — в Lua это разрешено, но локальные переменные предпочтительнее). Оператор .. склеивает строки. Функция print печатает аргументы и добавляет перевод строки; несколько аргументов она разделяет табуляцией.

Синтаксис: переменные и типы данных

Lua — язык с динамической типизацией: тип принадлежит значению, а не переменной, поэтому одна и та же переменная может по очереди хранить число, строку или таблицу. Объявлять тип заранее не нужно.

В Lua 5.4 восемь базовых типов: nil, boolean, number, string, function, table, thread (сопрограммы) и userdata (данные из внешнего кода на C). Коротко о ключевых:

  • nil — отсутствие значения; переменная без присвоенного значения равна nil.
  • boolean — true или false. Отдельно запомните: в условиях «ложными» считаются только false и nil, а число 0 и пустая строка — истинны.
  • number — числа. Начиная с Lua 5.3 у типа два подвида: целые (integer) и с плавающей точкой (float).
  • string — строки, неизменяемые (любая операция создает новую строку).
print(math.type(10))    -- integer
print(math.type(10.0))  -- float
print(7 // 2)           -- 3    целочисленное деление
print(7 / 2)            -- 3.5  обычное деление всегда дает float

Вывод:

integer
float
3
3.5

Функция math.type показывает подвид числа, а // — это целочисленное деление (округление вниз), тогда как / всегда возвращает float.

Множественное присваивание и функции

Lua умеет присваивать несколько значений за одну строку, поэтому обмен двух переменных пишется без временной переменной:

local a, b = 1, 2
a, b = b, a
print(a, b)   -- 2  1

Число переменных и значений слева и справа не обязано совпадать: лишние значения отбрасываются, недостающим присваивается nil. Функции — полноправные значения (их можно передавать в аргументы и возвращать), и они тоже умеют возвращать несколько результатов:

local function minmax(x, y)
  if x < y then
    return x, y
  end
  return y, x
end

local lo, hi = minmax(9, 4)
print(lo, hi)   -- 4    9

Функция minmax возвращает пару значений, которую мы сразу раскладываем в две переменные lo и hi.

Таблица — единственная структура данных

Ключевая особенность Lua: массив, словарь, список, множество, объект и даже пространство имен — все это одна и та же структура, таблица. Таблица — это ассоциативный массив пар «ключ-значение», где ключом может быть почти любое значение, кроме nil.

Как массив таблица индексируется с единицы (не с нуля, как во многих языках — это частый источник ошибок при переходе):

local t = {}          -- пустая таблица
t[1] = "a"            -- используем как массив
t[2] = "b"
t.color = "red"       -- та же таблица как словарь: t["color"]
print(#t)             -- 2    длина массивной части
print(t.color)        -- red

Вывод:

2
red

Оператор # дает длину массивной (целочисленной) части таблицы; ключ color в нее не входит. Запись t.color — это сокращение для t["color"].

Для перебора есть два способа: ipairs идет по целочисленным индексам подряд от 1, pairs обходит все ключи (порядок для нечисловых ключей не гарантируется).

local fruits = {"apple", "banana", "cherry"}
for i, v in ipairs(fruits) do
  print(i, v)
end

Вывод:

1   apple
2   banana
3   cherry

Литерал {"apple", "banana", "cherry"} — это та же таблица, где ключи 1, 2, 3 проставлены автоматически.

Метатаблицы: коротко о механизме поведения

Метатаблица — это обычная таблица, которую привязывают к другой таблице, чтобы задать ее поведение в особых ситуациях: сложение, сравнение, обращение к отсутствующему ключу. Специальные поля метатаблицы называются метаметодами (их имена начинаются с двух подчеркиваний). На метатаблицах в Lua строят объектную модель, потому что отдельного синтаксиса классов в языке нет.

Ниже минимальный пример: сделаем тип «вектор» и научим оператор + складывать два вектора через метаметод __add.

local Vector = {}
Vector.__index = Vector

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

function Vector.__add(a, b)
  return Vector.new(a.x + b.x, a.y + b.y)
end

local v = Vector.new(1, 2) + Vector.new(3, 4)
print(v.x, v.y)   -- 4  6

Вывод:

4   6

Функция setmetatable привязывает метатаблицу Vector к новому объекту. Когда интерпретатор встречает + между двумя такими объектами, он вызывает метаметод __add. Это упрощенная модель объектов: полноценные наследование и приватность делаются теми же метатаблицами, но подробности выходят за рамки знакомства с языком.

Типичная ошибка новичка

Обращение к полю значения, которого нет, дает ошибку выполнения, а не «тихий» nil:

local user = {name = "Ana"}
print(user.age.years)

Результат (текст и префикс зависят от способа запуска):

lua: hello.lua:2: attempt to index a nil value (field 'age')

Поля age в таблице нет, значит user.age равно nil, а обращение к .years у nil недопустимо. Правильно — сначала проверить наличие значения:

local user = {name = "Ana"}
print(user.age)             -- nil, это не ошибка
print(user.age and user.age.years)  -- nil, безопасное обращение

Идиома a and a.b возвращает nil, если a равно nil, и не роняет программу.

Где применяется Lua

Сильная сторона Lua — встраивание: интерпретатор занимает сотни килобайт и легко подключается к программе на C или C++, отдавая сценаристам и пользователям безопасный способ менять логику без пересборки основного приложения. Отсюда и типичные ниши.

Область Как используется Примеры
Игры Скрипты игровой логики поверх движка на C++ Движок LOVE (LOVE2D), аддоны World of Warcraft
Игровые платформы Встроенный язык сценариев для пользователей Roblox (диалект Luau на основе Lua)
Веб и прокси Обработка запросов внутри веб-сервера nginx через OpenResty (на LuaJIT)
Базы данных Серверные скрипты на стороне хранилища Redis (команда EVAL исполняет Lua)
Приложения Конфигурация и расширения (плагины) Встраивание в утилиты на C/C++

Два уточнения, чтобы не путать близкие вещи. Roblox использует не «чистый» Lua, а собственный диалект Luau (типизированное расширение на базе Lua 5.1). OpenResty работает не на эталонной виртуальной машине, а на LuaJIT — отдельной реализации с JIT-компиляцией, совместимой с Lua 5.1. Это разные реализации одной идеи языка, и версия там может отличаться от эталонной 5.4.

Немного истории

Lua создан в 1993 году в Католическом университете Рио-де-Жанейро (PUC-Rio); авторы — Роберто Иерусалимски, Вальдемар Селес и Луис Энрике ди Фигейреду. Предшественниками были внутренние языки SOL и DEL. Название Lua по-португальски означает «луна». С самого начала язык распространяется с открытым исходным кодом и написан на переносимом ANSI C — отсюда его портируемость между операционными системами.

Выводы

  • Lua — легкий встраиваемый скриптовый язык; чаще всего он живет внутри программы на C или C++, а не как самостоятельный проект.
  • Lua интерпретируемый: исходник компилируется во внутренний байткод и исполняется виртуальной машиной Lua; luac лишь заранее готовит байткод, но не машинный код платформы.
  • Таблица — единственная структура данных, которая играет роль и массива (индексы с 1), и словаря, и основы объектов.
  • Метатаблицы задают поведение таблиц (операторы, доступ к полям) и заменяют отдельный синтаксис классов.
  • Основные ниши: игры, встраивание в приложения, nginx/OpenResty и Redis; реализации бывают разные (эталонная ВМ, LuaJIT, Luau).

Где применяется / связь с практикой

Lua редко изучают в вакууме: его ценность раскрывается там, где скриптовый слой встроен в системную программу на C или C++ — игровой движок, сетевой прокси, хранилище данных. Чтобы уверенно связывать такой слой с ядром приложения, нужно понимать управление памятью, сборку и вызовы C API — это уже область системного программирования.

Освойте тему на практике

Разобраться с тем, как встраиваемые языки стыкуются с кодом на C/C++, помогает курс Программист C/C++: системная разработка. Если хочется сначала посмотреть формат и уровень без обязательств, загляните на открытые уроки Otus — там разбирают практические темы вживую.

FAQ

Lua и Luau — это одно и то же?
Нет. Lua — эталонный язык (актуальная ветка 5.4), а Luau — его отдельный диалект от Roblox с необязательной статической типизацией, построенный на базе Lua 5.1. Код на «чистом» Lua 5.4 не гарантированно запустится в Luau и наоборот.

Можно ли собрать из Lua-скрипта самостоятельный exe-файл?
Утилита luac дает файл байткода, но для его запуска все равно нужен интерпретатор. Отдельный исполняемый файл получают сторонними упаковщиками, которые кладут интерпретатор и скрипт в один бинарник; это не входит в стандартную поставку языка.

Почему индексация массивов начинается с 1, а не с 0?
Это решение авторов языка: в Lua целочисленные ключи таблиц по соглашению идут с 1, и на это рассчитаны ipairs, оператор # и стандартные библиотеки. Технически ключом может быть и 0, но тогда стандартные механизмы длины и перебора будут работать не так, как ожидается.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)