2 – Базовые понятия

Данный раздел описывает основные понятия языка.

2.1 – Значения и типы

Lua является языком динамической типизации, т.е. тип данных определяется динамически. Это означает что переменные не имеют типов; они есть только у значений. В языке нет определений типа. Все значения привносят свой собственный тип.

Все значения в Lua являются значениями первого класса. Это означает что все значения могут сохраняться в переменных, передаваться в другие функции в качестве аргументов и возвращаться в виде результата.

В Lua имеются восемь основных типов:
nil - ничто,
boolean - логика,
number - число,
string - строка,
function - функция,
userdata - пользовательские данные,
thread - нить (поток), и
table - таблица.
Тип nil (ничто) имеет одно единственное значение - nil, чье основное свойство - отличаться от любого другого значения; что обычно означает отсутствие целевого значения.
Тип boolean (логика) имеет два значения, false - ложь и true - истина. И значение nil и значение false делают условие ложным; любые другие значения делают его истинным.
Тип number (число) представлен как целыми, так и реальными (с плавающей запятой) числами.
Тип string (строка) представлен неизменяемыми последовательностями байтов. Lua полностью 8-битный: строки могут содержать любое 8-битное значение, включая встроенные нули('\0'). Lua также является агностиком в кодировании; он не делает никаких предположений о содержимом строки.
Тип number (число) использует два внутренних представления, или два подтипа, один называется integer (целые числа) а другой - float (числа с плавающей запятой). Lua имеет четкие правила о том, когда используется каждое из этих представлений, впрочем он также, при необходимости, автоматически конвертирует их между собой (смотрите §3.4.3). Следовательно, программист может выбирать, и либо, в основном игнорировать разницу между целыми числами и числами с плавающей запятой, либо принимать на себя полное управление над представлением каждого числа.
Стандартно Lua использует 64-битные целые числа и числа с плавающей запятой двойной точности (64-битные), но также можно так скомпилировать Lua, что он будет использовать 32-битные целые числа и/или числа с плавающей запятой одинарной точности (32-битные). Вариант с 32 битами для обоих подтипов чисел, целых и с плавающей запятой, особенно привлекателен для небольших машин и встраиваемых систем. (Смотрите макрос LUA_32BITS в файле luaconf.h.)

Lua может вызывать (и обрабатывать) функции написанные на Lua и функции написанные на C (смотрите §3.4.10). Оба вида функций представляют тип function.

Тип userdata (пользовательские данные) предоставляет возможность сохранять произвольные данные C в переменных Lua. Значение userdata представляет собой блок необработанных данных из оперативной памяти. Существует два вида данных типа userdata: full userdata (т.е. полноценные объекты userdata), которые являются объектом с блоком памяти, управляемым Lua, и light userdata (легкие объекты userdata), которые являются просто значением указателя C. Userdata не имеют предопределенных операций в Lua, за исключением присваивания и проверки идентичности. С помощью метатаблиц, программист может определять операции для значений full userdata (смотрите §2.4). Значения userdata нельзя создать или изменить в Lua, только через API C. Это гарантирует целостность данных, принадлежащих хост-программе.

Тип thread (нить) представляют собой независимые нити (потоки) исполнения кода и используются для реализации сопрограмм (coroutine) (смотрите §2.6). Нити Lua не связаны с нитями-потоками операционной системы. Lua поддерживает сопрограммы на всех системах, даже на тех, которые изначально не поддерживают нити.

Тип table (таблица) реализует ассоциативные массивы, то есть, массивы индексированные не только числами, но и любыми значениями Lua кроме nil и NaN.
(NaN - Not a Number (не число) является специальным значением для представления неопределенных или непредставимых результатов, таких как 0/0.)
Таблицы могут быть разнородными (heterogeneous); то есть, они могут содержать значения всех типов (за исключением nil). Любой ключ со значением nil не считается частью таблицы. И наоборот, любой ключ, который не является частью таблицы, имеет сопутствующее значение nil.

Таблицы в Lua являются основой механизма структуризации данных; они могут использоваться для представления обычных массивов, последовательностей, таблиц символов, отчетов, графов, древовидных структур и т.д. При представлении отчетов, в качестве индекса Lua использует поле name (имя). Язык поддерживает это представление предоставляя в качестве "синтаксического сахара" a.name для a["name"].
Примечание: Синтаксический сахар (англ. syntactic sugar) в языке программирования — это синтаксические возможности, применение которых не влияет на поведение программы, но делает использование языка более удобным для человека. ... читать далее
Это может быть любой элемент синтаксиса, который даёт программисту альтернативный способ записи уже имеющейся в языке синтаксической конструкции, и при этом является более удобным, или более кратким, или похожим на другой распространённый способ записи, или помогает писать программы в хорошем стиле.

(Из Википедии)   
В Lua имеется несколько удобных способов создания таблиц (смотрите §3.4.9).

Мы используем термин последовательность (sequence) для обозначения таблицы, где набор всех положительных числовых ключей равен {1..n} для некоторого неотрицательного целого числа n, которое называется длиной последовательности (смотрите §3.4.7).

Аналогично индексам, значения полей таблицы могут быть любого типа. В частности, поскольку функции являются значениями первого класса, поля таблицы могут содержать функции. Таким образом, таблицы могут переносить также и методы (смотрите §3.4.11).

Индексирование таблиц в языке следует определению необработанного равенства. Выражения a[i] и a[j] обозначают один и тот же элемент таблицы если и только если i и j равны без обработки (то есть, равны без метаметодов). В частности, числа с плавающей запятой с целочисленными значениями равны соответствующим им целых числам (например, 1.0 == 1). Чтобы уйти от неоднозначности, любые числа с плавающей запятой с целочисленным значением, используемые в качестве ключа, конвертируются в соответствующие им целые числа. Например, если написать a[2.0] = true, то на самом деле ключ, вставленный в таблицу, будет равен целому числу 2. (С другой стороны, 2 и "2" являются в Lua разными значениями и, следовательно, обозначают разные записи в таблице.)

Таблицы, функции, нити (потоки) и значения userdata (full) являются объектами: переменные на самом деле не содержат эти значения, только ссылки на них. Присвоение, прохождение параметра и возврат функции всегда управляют ссылками на подобные значения; эти операции не предполагают какого-либо вида копирования.

Библиотечная функция type возвращает строку с описанием типа заданного значения (смотрите §6.1).

2.2 – Окружение и глобальная среда

Как будет обсуждаться в §3.2 и §3.3.3, любая ссылка на свободное имя (то есть, имя не связанное с любым объявлением) var синтаксически преобразуется в _ENV.var. Более того, каждая порция (chunk) компилируется в области видимости внешней локальной переменной именуемой _ENV (смотрите §3.3.2), так что собственно сам _ENV не является в порции (chunk) свободным именем.

Несмотря на существование этой внешней переменной _ENV и преобразование свободных имен, _ENV является полностью обычным именем. В частности, с этим именем можно устанавливать новые переменные и параметры. Любая ссылка на свободное имя использует _ENV, которая видима в этом месте программы, следуя обычным правилам видимости Lua (смотрите §3.5).

Любая таблица, используемая в качестве значения _ENV, называется средой или окружением (environment).

Lua содержит известную среду называемую глобальным окружением (global environment). Это значение хранится под особым индексом в реестр C (смотрите §4.5). В Lua, глобальная переменная _G инициализирована с этим же значением. (_G внутренне никогда не используется.)

Когда Lua загружает порцию (chunk), значением по умолчанию для её внешней локальной переменной (upvalue) _ENV является глобальное окружение (смотрите load). Следовательно, по умолчанию, свободные имена в коде Lua ссылаются на записи в глобальной среде (поэтому они и называются глобальные переменные). Более того, все стандартные библиотеки загружены в глобальную среду и некоторые функции там работают в этой среде. Для загрузки порции (chunk) с разным окружением можно использовать load (или loadfile). (В C, нужно загрузить порцию и затем изменить значение ее первой внешней локальной переменной.)

2.3 – Обработка ошибок

Поскольку Lua является встраиваемым дополнительным языком, все действия Lua начинаются из кода C в хост-программе, вызывающего функцию из библиотеки Lua. (При использовании автономной Lua, приложение lua является хост-программой.) Всякий раз, когда происходит ошибка во время компиляции или выполнения порции Lua-кода (chunk), управление возвращается к хосту, который может принять соответствующие меры (например, вывести сообщение об ошибке).

Код Lua может явно генерировать ошибку вызывая функцию error. Если нужно отлавливать ошибки в Lua, можно использовать pcall или xpcall для вызова данной функции в защищенном режиме.

Каждый раз при ошибке, с информацией об ошибке распространяется объект ошибки (также называемый сообщение об ошибке (error message)). Собственно сам Lua генерирует только ошибки, чьим объектом ошибки является строка, но программы могут генерировать ошибки с любым значением в качестве объекта ошибки. Так сделано для программы Lua или ее хоста для обработки подобных объектов ошибки.

При использовании xpcall или lua_pcall, можно задавать обработчик сообщений (message handler) для вызова его в случае ошибок. Эта функция вызывается с исходным сообщением об ошибке, а возвращается с новым сообщением об ошибке. Она вызывается прежде чем ошибка разворачивает стек, так что она может собрать больше сведений об ошибке, например проверкой стека и созданием трассировки стека. Такой обработчик сообщений остается под охраной защищенного вызова; так, ошибка внутри обработчика сообщений вновь вызовет обработчик сообщений. Если этот цикл продолжается слишком долго, Lua прерывает его и возвращает соответствующее сообщение.

2.4 – Метатаблицы и метаметоды

Любое значение в Lua может иметь метатаблицу. Эта метатаблица - обычная Lua таблица, которая определяет поведение исходного значения при некоторых специальных операциях. Некоторые стороны поведения операций над значением можно изменять установкой конкретных полей этой метатаблицы. Например, когда нечисловое значение является операндом сложения, Lua проверяет наличие функции в поле "__add" метатаблицы этого значения. Если она там имеется, Lua вызывает эту функцию для выполнения сложения.

Ключи в метатаблице являются производными от названий событий; соответствующие значения называются метаметодами. В предыдущем примере, событие - это "add", а метаметодом является функция, которая выполняет сложение.

Запросить метатаблицу любого значения можно с помощью функции getmetatable.

Также можно заменять метатаблицы таблиц используя функцию setmetatable. Изменять метатаблицы других типов из Lua кода нельзя (за исключением библиотеки отладки (§6.10)); для этого следует использовать C-ишный API.

Таблицы и полноценные userdata имеют отдельные метатаблицы (хотя несколько таблиц и userdata могут использовать свои метатаблицы совместно). Значения всех других типов используют одну отдельную метатаблицу на каждый тип; то есть, имеется одна единственная метатаблица для всех чисел, другая - для всех строк, и т.д. По умолчанию, у значения нет метатаблицы, но строковая библиотека устанавливает метатаблицу для значений типа строка (смотрите §6.4).

Метатаблица управляет поведением объекта в арифметических операциях, побитовых операциях, порядке сравнения, конкатенации, операции длины, вызовах и индексировании. Метатаблица также может определять функцию, которая будет вызвана когда userdata или таблица обрабатываются сборщиком мусора (§2.5).

Ниже приводится подробный список событий, управляемых метатаблицами. Каждая операция определяется по, соответствующему ей, названию события. Ключом для каждого события является строка с его названием и префиксом в виде двух символов подчеркивания, '__'; например, ключом для операции "add" (сложение) является строка "__add". Обратите внимание, что запросы метаметодов всегда выполняются напрямую; обращение к метаметоду не вызывает других метаметодов.

Для унарных операторов (отрицание, длина и побитовый НЕ), метаметод вычисляется и вызывается с фиктивным вторым операндом, равным первому. Этот дополнительный операнд применяется только для упрощения внутри Lua (заставляя эти операторы вести себя подобно бинарным операциям) и может быть удален в будущих версиях. (Для большинства применений этот дополнительный операнд не имеет никакого значения.) Хорошим обычаем является добавление всех нужных метаметодов в таблицу, перед установкой её в качестве метатаблицы какого-либо объекта. В частности, метаметод "__gc" работает только когда этот порядок соблюдается (смотрите §2.5.1).

2.5 – Сборка мусора

Lua выполняет автоматическое управление памятью. Это значит, что не нужно беспокоиться о выделении памяти для новых объектов или высвобождении её, когда Lua реализует пошаговый сборщик "пометить и вымести" (mark-and-sweep).объекты больше не нужны. Lua управляет памятью автоматически запуская в работу сборщик мусора для сбора всех мертвых объектов (то есть, объектов, которые больше недоступны и Lua). Вся память, используемая Lua, подлежит автоматическому управлению: строки, таблицы, userdata, функции, потоки, внутренние структуры и т.д.

Для управления своими циклами сборки мусора, он использует два числа: пауза в сборке мусора и множитель шага сборки мусора. В качестве единиц измерения оба используют проценты (например, значение 100 означает внутреннее значение 1).

Пауза в сборке мусора определяет как долго сборщик мусора будет ожидать перед запуском нового цикла. Большие значения делают сборщик менее агрессивным. Значения меньше 100 означают, что сборщик не будет ждать для запуска нового цикла. Значение 200 означает, что сборщик мусора перед началом нового цикла ждет удвоения в использовании общего объема памяти.

Множитель шага сборки мусора управляет взаимосвязанной скоростью сборки мусора относительно выделения памяти. Большие значения делают сборщик более агрессивным, но также увеличивают размер каждого последующего шага. Не следует использовать значения меньше 100, так как они делают сборщик мусора медленным и в результате он может никогда не закончить цикл. Значение по умолчанию равно 200, это значит что сборщик работает на "удвоение" скорости выделения памяти.

Если в качестве шага множителя установить очень большое число (больше 10% от максимального числа байт, которые может использовать программа), то сборщик мусора будет вести себя как сборщик "останови-мир". Если установить паузу как 200, сборщик мусора поведет себя как в старых версиях Lua, делая полную сборку мусора каждый раз, когда Lua удваивает объем используемой им памяти.

Эти числа можно изменять, вызывая в C функцию lua_gc или вызывая в Lua функцию collectgarbage. Эти функции также можно использовать для непосредственного управления сборщиком мусора (например, остановкой или перезапуском его).

2.5.1 – Метаметоды сборщика мусора

Можно устанавливать метаметоды сборщика мусора для таблиц и, используя C-ишный API, для полноценных userdata (смотрите §2.4). Эти метаметоды также называются финализаторами. Финализаторы позволяют скоординировать сборку мусора Lua с внешним управлением ресурсами (как например закрытие файлов, подключения к сети или к базам данных, или высвобождение своей собственной памяти).

Чтобы объект (таблица или userdata) был завершен при сборке мусора, нужно пометить его для финализации. Для финализации объект помечается при установке его метатаблицы и эта метатаблица имеет поле, индексированное строкой "__gc". Обратите внимание, что если установить таблицу без поля __gc и позже создать такое поле в метатаблице, объект не будет отмечен для финализации.

Когда отмеченный объект становится мусором, он не убирается сразу сборщиком мусора. Вместо этого Lua вносит его в список. После сборки, Lua проходит по этому списку. Для каждого объекта из этого списка, он проверяет наличие метаметода __gc у объекта: если это функция, Lua вызывает её с объектом в качестве её единственного аргумента; если метаметод не является функцией, Lua просто игнорирует его.

В конце каждого цикла сборки мусора, финализаторы для объектов, собранных в данном цикле, вызываются в порядке обратном тому, в котором объекты были помечены для финализации; то есть, первым вызванным финализатором, будет тот у которого объект, с ним связанный, в программе был помечен последним. Выполнение каждого финализатора может происходить в любой момент при выполнении очередного кода.

Поскольку объект будучи собранным должен оставаться в использовании финализатором, этот объект (и другие объекты доступные только через него) должен быть возрожден в Lua. Обычно, такое возрождение временное, и память, занятая объектом, высвобождается в следующем цикле сборки мусора. Однако, если финализатор сохраняет объект в каком-нибудь глобальном месте (например, глобальная переменная), то такое возрождение является постоянным. Более того, если финализатор помечает финализированный объект для повторной финализации, то его финализатор будет вызван снова в следующем цикле, где объект недоступен. В любом случае, память, занимаемая объектом, высвобождается только в цикле сборки мусора, где объект недоступен и не отмечен для финализации.

При закрытии состояния (смотрите lua_close), Lua вызывает финализаторы всех объектов, отмеченных для финализации, следуя порядку, обратном тому, в котором они были помечены. Если какой-либо финализатор помечает объекты для сборки во время этой фазы, то такие отметки уже не оказывают никакого влияния.

2.5.2 – Слабые таблицы

Слабые таблицы (weak table) - это таблицы, чьи элементы являются слабыми ссылками. Слабая ссылка игнорируется сборщиком мусора. Иначе говоря, если ссылки на объект являются исключительно слабыми ссылками, то сборщик мусора приберет этот объект.

Слабая таблица может иметь слабые ключи, слабые значения или и то, и другое вместе. Таблица со слабыми значениями позволяет собирать свои значения, но препятствует сбору своих ключей. Таблица со слабыми и ключами и значениями позволяет собирать как ключи, так и значения. В любом случае, если собраны либо ключи, либо значения, пара целиком удаляется из таблицы. "Слабость" таблицы управляется полем __mode её метатаблицы. Если поле __mode - это строка, содержащая символ  'k', то ключи в этой таблице слабые. Если поле __mode содержит 'v', то слабыми в таблице будут значения.

Таблица со слабыми ключами и сильными значениями также называется эфемерной таблицей. В эфемерной таблице значение считается доступным, только если доступен его ключ. В частности, если ссылка на ключ приходит только через его значение, то пара удаляется.

Любое изменение "слабости" таблицы может вступить в силу только при следующем цикле сборки мусора. В особенности, если изменять слабость к более сильному режиму, Lua может по-прежнему получать некоторые элементы из этой таблицы, прежде чем изменения вступять в силу.

Из слабых таблиц удаляются только объекты, которые имеют явную конструкцию. Значения, подобные числам и легким С-ишным функциям, не подлежат сборке мусора и поэтому не удаляются из слабых таблиц (пока значения, связанные с ними, не собраны). Несмотря на то, что строки подлежат сборке мусора, они не имеют явной конструкции и, следовательно, не удаляются из слабых таблиц.

Возрожденные объекты (то есть, финализированные объекты и объекты, доступные только через финализированные объекты) имеют особое поведение в слабых таблицах. Они удаляются из слабых значений перед запуском их финализаторов, но они удаляются из слабых ключей только при последующей сборке мусора, после запуска их финализаторов, когда подобные объекты фактически высвобождены. Такое поведение позволяет финализатору получить доступ к свойствам, связанным с объектом через слабые таблицы.

Если в цикле сборки мусора слабая таблица появляется среди возрожденных объектов, она не может быть должным образом вычищена до следующего цикла.

2.6 – Сопрограммы

Lua поддерживает сопрограммы, также называемые совместной многопоточностью (collaborative multithreading). Сопрограмма в Lua представляет собой независимый поток (или нить) выполнения. Однако, в отличие от потоков в многопоточных системах, сопрограмма приостанавливает свое выполнение только по явному вызову функции выхода (yield).

Сопрограмма создается вызовом функции coroutine.create. Её единственным аргументом является основная функция сопрограммы. Функция create только создает новую сопрограмму и возвращает её дескриптор (handle) (объект типа thread); она не запускает сопрограмму.

Выполняется сопрограмма вызовом coroutine.resume. При первом вызове coroutine.resume, передавая в качестве своего первого аргумента поток, возвращенный coroutine.create, сопрограмма начинает свое выполнение вызовом своей основной функции. Дополнительные аргументы переданные coroutine.resume передаются этой функции как аргументы. После запуска сопрограммы, она будет работать до своего завершения или до yields.

Сопрограмма может завершить свое выполнение двумя способами: обычным (нормальным), когда возвращается её основная функция (прямо или косвенно после последней инструкции); и ненормально, если имеется открытая ошибка. В случае нормального завершения, coroutine.resume возвращает true, плюс любые значения, возвращаемые основной функцией сопрограммы. В случае ошибок, coroutine.resume возвращает false плюс сообщение об ошибке.

Выполнение сопрограммы приостанавливается вызовом функции coroutine.yield. Когда сопрограмма приостанавливается, соответствующая функция coroutine.resume сразу возвращается, даже если выход из выполнения происходит внутри вызовов вложенной функции (то есть, не в основной функции, а в функции, прямо или косвенно, вызванной основной функцией). В случае приостановки выполнения (выхода), coroutine.resume также возвращает true, плюс любые значения, переданные coroutine.yield. В следующий раз, при возобновлении той же сопрограммы, она начнет выполняться в точке, где была приостановлена, с вызова coroutine.yield возвращении любых дополнительных аргументов переданных coroutine.resume.

Подобно функции coroutine.create, функция coroutine.wrap также создает сопрограмму, но вместо возвращения собственно сопрограммы, она возвращает функцию, которая при вызове, возобновляет сопрограмму. Любые аргументы, переданные в эту функцию, проходят как дополнительные аргументы в coroutine.resume. coroutine.wrap возвращает все значения, возвращенные coroutine.resume, за исключением первого из них (логический код ошибки). В отличие от coroutine.resume, coroutine.wrap не перехватывает ошибки; любая ошибка передается функции, вызвавшей coroutine.wrap.

В качестве примера того, как работают сопрограммы, рассмотрим следующий код:
function foo (a)
  print("foo", a)
  return coroutine.yield(2*a)
end
co = coroutine.create(function (a,b)
      print("co-body", a, b)
      local r = foo(a+1)
      print("co-body", r)
      local r, s = coroutine.yield(a+b, a-b)
      print("co-body", r, s)
      return b, "end"
end)
print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))
Если его запустить, он выдаст следущие данные:
co-body 1       10
foo     2
main    true    4
co-body r
main    true    11      -9
co-body x       y
main    true    10      end
main    false   cannot resume dead coroutine
Также создавать и управлять сопрограммами можно через C-ишный API: посмотрите описание функций lua_newthread, lua_resume, и lua_yield.