2 – Основные понятия
В этом разделе описываются основные понятия языка.
2.1 – Значения и типы
Lua — язык с динамической типизацией. Это означает, что переменные не имеют типов; только значения имеют. В языке нет определений типов. Все значения несут свой собственный тип.
Все значения в Lua являются полноправными значениями. Это означает, что все значения могут храниться в переменных, передаваться в качестве аргументов другим функциям и возвращаться в качестве результатов.
В Lua есть восемь базовых типов: nil, boolean, number, string, function, userdata, thread и table. Тип nil имеет единственное значение, nil, главное свойство которого — отличаться от любого другого значения; он часто представляет отсутствие полезного значения. Тип boolean имеет два значения: false и true. И nil, и false делают условие ложным; вместе они называются ложными значениями. Любое другое значение делает условие истинным. Несмотря на своё название, false часто используется как альтернатива nil, с ключевым отличием: false ведёт себя как обычное значение в таблице, тогда как nil в таблице представляет отсутствующий ключ.
Тип number представляет как
целые числа, так и вещественные (с плавающей точкой) числа,
используя два подтипа: integer и float.
Стандартная Lua использует 64-битные целые числа и числа с плавающей точкой
двойной точности (64-битные),
но можно также собрать Lua так, чтобы она
использовала 32-битные целые числа и/или числа с плавающей точкой одинарной точности (32-битные).
Вариант с 32 битами как для целых чисел, так и для чисел с плавающей точкой
особенно привлекателен
для малых машин и встраиваемых систем.
(См. макрос LUA_32BITS в файле luaconf.h.)
Если не указано иное, любое переполнение при работе с целочисленными значениями заворачивается согласно обычным правилам арифметики дополнения до двух. (Иными словами, фактический результат — это единственное представимое целое число, которое сравнимо по модулю 2n с математическим результатом, где n — количество битов целочисленного типа.)
В Lua есть явные правила о том, когда используется каждый подтип, но она также автоматически преобразует их друг в друга по мере необходимости (см. §3.4.3). Поэтому программист может предпочесть в основном игнорировать различие между целыми числами и числами с плавающей точкой или полностью контролировать представление каждого числа.
Тип string представляет неизменяемые последовательности байтов.
Lua способна работать с произвольными 8-битными данными (8-bit clean):
строки могут содержать любое 8-битное значение,
включая встроенные нули ('\0').
Lua также не зависит от кодировки;
она не делает никаких предположений о содержимом строки.
Длина любой строки в Lua должна помещаться в целое число Lua,
а строка плюс небольшой заголовок должны помещаться в size_t.
Lua может вызывать (и манипулировать) функциями, написанными на Lua, и функциями, написанными на C (см. §3.4.10). Оба вида представляются типом function.
Тип userdata предоставляется для того, чтобы произвольные данные C могли храниться в переменных Lua. Значение userdata представляет блок сырой памяти. Существует два вида userdata: полное userdata (full userdata), которое является объектом с блоком памяти, управляемым Lua, и лёгкое userdata (light userdata), которое является просто значением указателя C . Userdata не имеет предопределённых операций в Lua, кроме присваивания и проверки идентичности. Используя метатаблицы, программист может определить операции для значений полного userdata (см. §2.4). Значения userdata нельзя создавать или изменять в Lua, только через C API. Это гарантирует целостность данных, принадлежащих хост-программе и библиотекам C .
Тип thread представляет независимые потоки выполнения и используется для реализации сопрограмм (см. §2.6). Потоки Lua не связаны с потоками операционной системы. Lua поддерживает сопрограммы на всех системах, даже на тех, которые не поддерживают потоки нативно.
Тип table реализует ассоциативные массивы,
то есть массивы, индексами которых могут быть не только числа,
но и любое значение Lua, кроме nil и NaN.
(Not a Number — это специальное значение с плавающей точкой,
используемое стандартом IEEE 754 для представления
неопределённых числовых результатов, таких как 0/0.)
Таблицы могут быть неоднородными;
то есть они могут содержать значения всех типов (кроме nil).
Любой ключ, связанный со значением nil, не считается частью таблицы.
И наоборот, любой ключ, не являющийся частью таблицы, имеет
связанное значение nil.
Таблицы — единственный механизм структурирования данных в Lua;
они могут использоваться для представления обычных массивов, списков,
таблиц символов, множеств, записей, графов, деревьев и т.д.
Для представления записей Lua использует имя поля в качестве индекса.
Язык поддерживает это представление,
предоставляя a.name как синтаксический сахар для a["name"].
Существует несколько удобных способов создания таблиц в Lua
(см. §3.4.9).
Как и индексы, значения полей таблицы могут быть любого типа. В частности, поскольку функции являются полноправными значениями, поля таблицы могут содержать функции. Таким образом, таблицы могут также нести методы (см. §3.4.11).
Индексация таблиц следует
определению сырого равенства в языке.
Выражения a[i] и a[j]
обозначают один и тот же элемент таблицы
тогда и только тогда, когда i и j сыро равны
(то есть равны без метаметодов).
В частности, числа с плавающей точкой с целыми значениями
равны соответствующим целым числам
(например, 1.0 == 1).
Чтобы избежать неоднозначности,
любое число с плавающей точкой, используемое в качестве ключа и равное целому числу,
преобразуется в это целое число.
Например, если вы напишете a[2.0] = true,
фактическим ключом, вставленным в таблицу, будет целое число 2.
Таблицы, функции, потоки и значения (полного) userdata являются объектами: переменные на самом деле не содержат эти значения, а только ссылки на них. Присваивание, передача параметров и возврат из функций всегда манипулируют ссылками на такие значения; эти операции не подразумевают никакого копирования.
Библиотечная функция type возвращает строку, описывающую тип
заданного значения (см. type).
2.2 – Области видимости, переменные и окружения
Имя переменной относится к глобальной или локальной переменной в соответствии с объявлением, которое находится в контексте в данной точке кода. (Для целей данного обсуждения формальный параметр функции эквивалентен локальной переменной.)
Все чанки начинаются с неявного объявления global *,
которое объявляет все свободные имена глобальными переменными;
это вступительное объявление становится недействительным внутри области видимости любого другого
объявления global,
как показывает следующий пример:
X = 1 -- Ok, global by default
do
global Y -- voids the implicit initial declaration
Y = 1 -- Ok, Y declared as global
X = 1 -- ERROR, X not declared
end
X = 2 -- Ok, global by default again
Таким образом, вне любого объявления global Lua работает как «глобальные по умолчанию» (global-by-default). Внутри любого объявления global Lua работает без значения по умолчанию: все переменные должны быть объявлены.
Lua — язык с лексической областью видимости. Область видимости объявления переменной начинается с первого оператора после объявления и продолжается до последнего непустого оператора внутреннего блока, включающего объявление. (Пустые операторы — это метки и пустые инструкции.)
Объявление затеняет любое объявление для того же имени, которое находится в контексте в точке объявления. Внутри этого затенения любое внешнее объявление для этого имени недействительно. См. следующий пример:
global print, x
x = 10 -- global variable
do -- new block
local x = x -- new 'x', with value 10
print(x) --> 10
x = x+1
do -- another block
local x = x+1 -- another 'x'
print(x) --> 12
end
print(x) --> 11
end
print(x) --> 10 (the global one)
Обратите внимание, что в объявлении вроде local x = x
объявляемый новый x ещё не находится в области видимости,
поэтому x в правой части относится к внешней переменной.
Благодаря правилам лексической области видимости локальные переменные свободно доступны функциям, определённым внутри их области видимости. Локальная переменная, используемая внутренней функцией, называется upvalue (или внешней локальной переменной, или просто внешней переменной) внутри внутренней функции.
Обратите внимание, что каждое выполнение оператора local определяет новые локальные переменные. Рассмотрим следующий пример:
a = {}
local x = 20
for i = 1, 10 do
local y = 0
a[i] = function () y = y + 1; return x + y end
end
Цикл создаёт десять замыканий
(то есть десять экземпляров анонимной функции).
Каждое из этих замыканий использует свою переменную y,
тогда как все они совместно используют одну и ту же x.
Как мы обсудим далее в §3.2 и §3.3.3,
любая ссылка на глобальную переменную var
синтаксически транслируется в _ENV.var.
Кроме того, каждый чанк компилируется в области видимости
внешней локальной переменной с именем _ENV (см. §3.3.2),
поэтому _ENV сам по себе никогда не является свободным именем в чанке.
Несмотря на существование этой внешней переменной _ENV и
трансляцию свободных имён,
_ENV является обычным именем.
В частности,
вы можете определять новые переменные и параметры с этим именем.
(Однако не следует определять _ENV как глобальную переменную,
иначе _ENV.var транслировалось бы в
_ENV._ENV.var и так далее, в бесконечном цикле.)
Каждая ссылка на имя глобальной переменной использует _ENV, которая
видима в данной точке программы.
Любая таблица, используемая в качестве значения _ENV, называется окружением.
Lua хранит особое окружение, называемое глобальным окружением.
Это значение хранится по специальному индексу в реестре C (см. §4.3).
В Lua глобальная переменная _G инициализируется этим же значением.
(_G никогда не используется внутри,
поэтому изменение её значения повлияет только на ваш собственный код.)
Когда Lua загружает чанк,
значением по умолчанию для его переменной _ENV
является глобальное окружение (см. load).
Поэтому по умолчанию
глобальные переменные в коде Lua ссылаются на записи в глобальном окружении
и, следовательно, действуют как обычные глобальные переменные.
Кроме того, все стандартные библиотеки загружаются в глобальное окружение,
и некоторые функции там работают с этим окружением.
Вы можете использовать load (или loadfile),
чтобы загрузить чанк с другим окружением.
(В C необходимо загрузить чанк, а затем изменить значение
его первой upvalue; см. lua_setupvalue.)
2.3 – Обработка ошибок
Некоторые операции в Lua могут вызывать ошибку. Ошибка прерывает нормальный поток программы, который может продолжиться перехватом ошибки.
Код Lua может явно вызвать ошибку, вызвав функцию
error.
(Эта функция никогда не возвращает управление.)
Чтобы перехватить ошибки в Lua,
можно выполнить защищённый вызов,
используя pcall (или xpcall).
Функция pcall вызывает заданную функцию в защищённом режиме.
Любая ошибка во время выполнения функции останавливает её выполнение,
и управление немедленно возвращается в pcall,
который возвращает код состояния.
Поскольку Lua — встраиваемый язык расширения,
код Lua начинает выполняться по вызову
из кода C в хост-программе.
(Когда вы используете Lua автономно,
приложение lua является хост-программой.)
Обычно этот вызов является защищённым;
поэтому, когда иначе незащищённая ошибка происходит во время
компиляции или выполнения чанка Lua,
управление возвращается к хосту,
который может принять соответствующие меры,
например напечатать сообщение об ошибке.
Всякий раз, когда происходит ошибка, распространяется объект ошибки с информацией об ошибке. Сама Lua генерирует только ошибки, объект ошибки которых является строкой, но программы могут генерировать ошибки с любым значением в качестве объекта ошибки, кроме nil. (Lua заменит nil в качестве объекта ошибки на строковое сообщение.) Обработка таких объектов ошибок возлагается на программу Lua или её хост. По историческим причинам объект ошибки часто называют сообщением об ошибке, хотя он не обязан быть строкой.
При использовании xpcall (или lua_pcall в C)
можно задать обработчик сообщений,
который будет вызываться в случае ошибок.
Эта функция вызывается с исходным объектом ошибки
и возвращает новый объект ошибки.
Она вызывается до того, как ошибка размотает стек,
чтобы можно было собрать больше информации об ошибке,
например, путём исследования стека и создания трассировки стека.
Этот обработчик сообщений по-прежнему защищён защищённым вызовом;
поэтому ошибка внутри обработчика сообщений
снова вызовет обработчик сообщений.
Если этот цикл продолжается слишком долго,
Lua прерывает его и возвращает соответствующее сообщение.
Обработчик сообщений вызывается только для обычных ошибок времени выполнения.
Он не вызывается для ошибок выделения памяти,
а также для ошибок при выполнении финализаторов или других обработчиков сообщений.
Lua также предлагает систему предупреждений (см. warn).
В отличие от ошибок, предупреждения никак не вмешиваются
в выполнение программы.
Обычно они лишь генерируют сообщение пользователю,
хотя это поведение можно адаптировать из C (см. lua_setwarnf).
2.4 – Метатаблицы и метаметоды
Каждое значение в Lua может иметь метатаблицу.
Эта метатаблица — обычная таблица Lua,
которая определяет поведение исходного значения
при определённых событиях.
Вы можете изменить несколько аспектов поведения
значения, задав определённые поля в его метатаблице.
Например, когда нечисловое значение является операндом сложения,
Lua проверяет наличие функции в поле __add метатаблицы этого значения.
Если она находит её,
Lua вызывает эту функцию для выполнения сложения.
Ключом для каждого события в метатаблице является строка
с именем события, перед которым стоят два подчёркивания;
соответствующее значение называется метазначением.
Для большинства событий метазначение должно быть функцией,
которая затем называется метаметодом.
В предыдущем примере ключом является строка "__add",
а метаметодом — функция, выполняющая сложение.
Если не указано иное,
метаметод на самом деле может быть любым вызываемым значением,
то есть либо функцией, либо значением с метаметодом __call.
Вы можете запросить метатаблицу любого значения
с помощью функции getmetatable.
Lua запрашивает метаметоды в метатаблицах с помощью сырого доступа (см. rawget).
Вы можете заменить метатаблицу таблиц
с помощью функции setmetatable.
Вы не можете изменить метатаблицу других типов из кода Lua,
за исключением использования библиотеки отладки (§6.11).
Таблицы и полное userdata имеют индивидуальные метатаблицы, хотя несколько таблиц и userdata могут совместно использовать свои метатаблицы. Значения всех других типов разделяют одну метатаблицу на тип; то есть существует одна метатаблица для всех чисел, одна для всех строк и т.д. По умолчанию значение не имеет метатаблицы, но библиотека строк устанавливает метатаблицу для типа string (см. §6.5).
Подробный список операций, управляемых метатаблицами, приведён ниже. Каждое событие идентифицируется своим соответствующим ключом. По соглашению все ключи метатаблиц, используемые Lua, состоят из двух подчёркиваний, за которыми следуют строчные латинские буквы.
__add: операция сложения (+). Если любой операнд сложения не является числом, Lua попытается вызвать метаметод. Она начинает с проверки первого операнда (даже если он число); если этот операнд не определяет метаметод для__add, то Lua проверит второй операнд. Если Lua может найти метаметод, она вызывает метаметод с двумя операндами в качестве аргументов, и результат вызова (скорректированный до одного значения) является результатом операции. В противном случае, если метаметод не найден, Lua вызывает ошибку.__sub: операция вычитания (-). Поведение аналогично операции сложения.__mul: операция умножения (*). Поведение аналогично операции сложения.__div: операция деления (/). Поведение аналогично операции сложения.__mod: операция взятия остатка (%). Поведение аналогично операции сложения.__pow: операция возведения в степень (^). Поведение аналогично операции сложения.__unm: операция отрицания (унарный-). Поведение аналогично операции сложения.__idiv: операция целочисленного деления с округлением вниз (//). Поведение аналогично операции сложения.__band: операция побитового И (&). Поведение аналогично операции сложения, за исключением того, что Lua попытается вызвать метаметод, если любой операнд не является ни целым числом, ни числом с плавающей точкой, приводимым к целому (см. §3.4.3).__bor: операция побитового ИЛИ (|). Поведение аналогично операции побитового И.__bxor: операция побитового исключающего ИЛИ (бинарный~). Поведение аналогично операции побитового И.__bnot: операция побитового НЕ (унарный~). Поведение аналогично операции побитового И.__shl: операция побитового сдвига влево (<<). Поведение аналогично операции побитового И.__shr: операция побитового сдвига вправо (>>). Поведение аналогично операции побитового И.__concat: операция конкатенации (..). Поведение аналогично операции сложения, за исключением того, что Lua попытается вызвать метаметод, если любой операнд не является ни строкой, ни числом (которое всегда приводимо к строке).__len: операция длины (#). Если объект не является строкой, Lua попытается вызвать его метаметод. Если метаметод есть, Lua вызывает его с объектом в качестве аргумента, и результат вызова (всегда скорректированный до одного значения) является результатом операции. Если метаметода нет, но объект является таблицей, то Lua использует операцию длины таблицы (см. §3.4.7). В противном случае Lua вызывает ошибку.__eq: операция равенства (==). Поведение аналогично операции сложения, за исключением того, что Lua попытается вызвать метаметод только тогда, когда сравниваемые значения являются либо обеими таблицами, либо обоими полными userdata и они не равны примитивно. Результат вызова всегда приводится к логическому значению.__lt: операция «меньше» (<). Поведение аналогично операции сложения, за исключением того, что Lua попытается вызвать метаметод только тогда, когда сравниваемые значения не являются ни оба числами, ни оба строками. Кроме того, результат вызова всегда приводится к логическому значению.__le: операция «меньше или равно» (<=). Поведение аналогично операции «меньше».__index: операция доступа по индексуtable[key]. Это событие происходит, когдаtableне является таблицей или когдаkeyотсутствует вtable. Метазначение ищется в метатаблицеtable.Метазначением для этого события может быть функция, таблица или любое значение с метазначением
__index. Если это функция, она вызывается сtableиkeyв качестве аргументов, и результат вызова (скорректированный до одного значения) является результатом операции. В противном случае конечным результатом является результат индексации этого метазначения с помощьюkey. Эта индексация является обычной, а не сырой, и поэтому может вызвать другое метазначение__index.__newindex: присваивание по индексуtable[key] = value. Как и событие индекса, это событие происходит, когдаtableне является таблицей или когдаkeyотсутствует вtable. Метазначение ищется в метатаблицеtable.Как и при индексации, метазначением для этого события может быть функция, таблица или любое значение с метазначением
__newindex. Если это функция, она вызывается сtable,keyиvalueв качестве аргументов. В противном случае Lua повторяет присваивание по индексу для этого метазначения с тем же ключом и значением. Это присваивание является обычным, а не сырым, и поэтому может вызвать другое метазначение__newindex.Всякий раз, когда вызывается метазначение
__newindex, Lua не выполняет примитивное присваивание. При необходимости сам метаметод может вызватьrawset, чтобы выполнить присваивание.__call: операция вызоваfunc(args). Это событие происходит, когда Lua пытается вызвать значение, не являющееся функцией (то естьfuncне является функцией). Метаметод ищется вfunc. Если он присутствует, метаметод вызывается сfuncв качестве первого аргумента, за которым следуют аргументы исходного вызова (args). Все результаты вызова являются результатами операции. Это единственный метаметод, который допускает несколько результатов.
В дополнение к предыдущему списку
интерпретатор также учитывает следующие ключи в метатаблицах:
__gc (см. §2.5.3),
__close (см. §3.3.8),
__mode (см. §2.5.4)
и __name.
(Запись __name,
если она содержит строку,
может использоваться функцией tostring и в сообщениях об ошибках.)
Для унарных операторов (отрицания, длины и побитового НЕ) метаметод вычисляется и вызывается с фиктивным вторым операндом, равным первому. Этот дополнительный операнд служит только для упрощения внутреннего устройства Lua (заставляя эти операторы вести себя как бинарная операция) и может быть удалён в будущих версиях. Для большинства применений этот дополнительный операнд несущественен.
Поскольку метатаблицы — обычные таблицы,
они могут содержать произвольные поля,
а не только имена событий, определённые выше.
Некоторые функции в стандартной библиотеке
(например, tostring)
используют другие поля в метатаблицах для своих целей.
Хорошей практикой является добавление всех необходимых метаметодов в таблицу
до того, как она будет установлена в качестве метатаблицы некоторого объекта.
В частности, метаметод __gc работает только при соблюдении этого порядка
(см. §2.5.3).
Также хорошей практикой является установка метатаблицы объекта
сразу после его создания.
2.5 – Сборка мусора
Lua выполняет автоматическое управление памятью. Это означает, что вам не нужно беспокоиться о выделении памяти для новых объектов или освобождении её, когда объекты больше не нужны. Lua управляет памятью автоматически, запуская сборщик мусора для сбора всех мёртвых объектов. Вся память, используемая Lua, подлежит автоматическому управлению: строки, таблицы, userdata, функции, потоки, внутренние структуры и т.д.
Объект считается мёртвым, как только сборщик может быть уверен, что объект не будет снова доступен при нормальном выполнении программы. («Нормальное выполнение» здесь исключает финализаторы, которые воскрешают мёртвые объекты (см. §2.5.3), а также исключает некоторые операции с использованием библиотеки отладки.) Обратите внимание, что момент, когда сборщик может быть уверен, что объект мёртв, может не совпадать с ожиданиями программиста. Единственные гарантии: Lua не соберёт объект, который может быть ещё доступен при нормальном выполнении программы, и в конечном итоге соберёт объект, недоступный из Lua. (Здесь недоступный из Lua означает, что ни переменная, ни другой живой объект не ссылаются на объект.) Поскольку Lua ничего не знает о коде C , она никогда не собирает объекты, доступные через реестр (см. §4.3), который включает глобальное окружение (см. §2.2) и главный поток.
Сборщик мусора (GC) в Lua может работать в двух режимах: инкрементальном и поколенческом.
Режим GC по умолчанию с параметрами по умолчанию подходит для большинства применений. Однако программы, которые тратят большую часть своего времени на выделение и освобождение памяти, могут выиграть от других настроек. Имейте в виду, что поведение GC непереносимо как между платформами, так и между различными выпусками Lua; поэтому оптимальные настройки также непереносимы.
Вы можете изменить режим и параметры GC, вызвав
lua_gc в C
или collectgarbage в Lua.
Вы также можете использовать эти функции для прямого управления сборщиком,
например, чтобы остановить или перезапустить его.
2.5.1 – Инкрементальная сборка мусора
В инкрементальном режиме каждый цикл GC выполняет сборку с пометкой и очисткой небольшими шагами, чередующимися с выполнением программы. В этом режиме сборщик использует три числа для управления своими циклами сборки мусора: пауза сборщика мусора, множитель шага сборщика мусора и размер шага сборщика мусора.
Пауза сборщика мусора управляет тем, как долго сборщик ждёт перед началом нового цикла. Сборщик начинает новый цикл, когда количество байтов достигает n% от общего объёма после предыдущей сборки. Большие значения делают сборщик менее агрессивным. Значения, равные или меньшие 100, означают, что сборщик не будет ждать, чтобы начать новый цикл. Значение 200 означает, что сборщик ждёт, пока общее количество байтов удвоится, прежде чем начать новый цикл.
Размер шага сборщика мусора управляет размером каждого инкрементального шага, а именно тем, сколько байтов интерпретатор выделяет перед выполнением шага: значение n означает, что интерпретатор выделит приблизительно n байтов между шагами.
Множитель шага сборщика мусора управляет тем, сколько работы выполняет каждый инкрементальный шаг. Значение n означает, что интерпретатор выполнит n% единиц работы для каждого выделенного слова. Единица работы приблизительно соответствует обходу одного слота или очистке одного объекта. Большие значения делают сборщик более агрессивным. Остерегайтесь слишком малых значений: сборщик может стать настолько медленным, что никогда не завершит цикл. В особом случае нулевое значение означает неограниченную работу, фактически создавая неинкрементальный сборщик с остановкой мира (stop-the-world).
2.5.2 – Поколенческая сборка мусора
В поколенческом режиме сборщик выполняет частые малые сборки, которые обходят только недавно созданные объекты. Если после малой сборки количество байтов превышает предел, сборщик переходит к большой сборке, которая обходит все объекты. Затем сборщик продолжает выполнять большие сборки, пока не обнаружит, что программа генерирует достаточно мусора, чтобы оправдать возврат к малым сборкам.
Поколенческий режим использует три параметра: множитель малых сборок, множитель малых к большим и множитель больших к малым.
Множитель малых сборок управляет частотой малых сборок. При множителе малых сборок x новая малая сборка будет выполнена, когда количество байтов станет на x% больше, чем количество используемых байтов сразу после последней большой сборки. Например, при множителе 20 сборщик выполнит малую сборку, когда количество байтов станет на 20% больше общего объёма после последней большой сборки.
Множитель малых к большим управляет переходом к большим сборкам. При множителе x сборщик перейдёт к большой сборке, когда количество байтов старых объектов станет на x% больше общего объёма после предыдущей большой сборки. Например, при множителе 100 сборщик выполнит большую сборку, когда количество старых байтов станет больше удвоенного общего объёма после предыдущей большой сборки. В особом случае значение 0 не позволяет сборщику выполнять большие сборки.
Множитель больших к малым управляет возвратом к малым сборкам. При множителе x сборщик вернётся к малым сборкам после того, как большая сборка соберёт как минимум x% байтов, выделенных за последний цикл. В частности, при множителе 0 сборщик немедленно вернётся к малым сборкам после выполнения одной большой сборки.
2.5.3 – Метаметоды сборки мусора
Вы можете устанавливать метаметоды сборщика мусора для таблиц и, используя C API, для полного userdata (см. §2.4). Эти метаметоды, называемые финализаторами, вызываются, когда сборщик мусора обнаруживает, что соответствующая таблица или userdata мертвы. Финализаторы позволяют согласовать сборку мусора Lua с управлением внешними ресурсами, такими как закрытие файлов, сетевых соединений или соединений с базами данных, или освобождение вашей собственной памяти.
Чтобы объект (таблица или userdata) был финализирован при сборке,
вы должны пометить его для финализации.
Вы помечаете объект для финализации, когда устанавливаете его метатаблицу,
и эта метатаблица имеет метаметод __gc.
Обратите внимание, что если вы установите метатаблицу без поля __gc
и позже создадите это поле в метатаблице,
объект не будет помечен для финализации.
Когда помеченный объект становится мёртвым,
он не собирается сразу сборщиком мусора.
Вместо этого Lua помещает его в список.
После сборки
Lua проходит по этому списку.
Для каждого объекта в списке
она проверяет метаметод __gc объекта:
если он присутствует,
Lua вызывает его с объектом в качестве единственного аргумента.
В конце каждого цикла сборки мусора финализаторы вызываются в порядке, обратном порядку, в котором объекты были помечены для финализации, среди собранных в этом цикле; то есть первый вызываемый финализатор — это тот, который связан с объектом, помеченным в программе последним. Выполнение каждого финализатора может происходить в любой момент во время выполнения обычного кода.
Поскольку собираемый объект всё ещё должен использоваться финализатором, этот объект (и другие объекты, доступные только через него) должны быть воскрешены Lua. Обычно это воскрешение является временным, и память объекта освобождается в следующем цикле сборки мусора. Однако если финализатор сохраняет объект в некотором глобальном месте (например, в глобальной переменной), то воскрешение является постоянным. Кроме того, если финализатор снова помечает финализируемый объект для финализации, его финализатор будет снова вызван в следующем цикле, где объект мёртв. В любом случае память объекта освобождается только в цикле GC, где объект мёртв и не помечен для финализации.
Когда вы закрываете состояние (см. lua_close),
Lua вызывает финализаторы всех объектов, помеченных для финализации,
в порядке, обратном порядку их помещения.
Если какой-либо финализатор помечает объекты для сборки во время этой фазы,
эти метки не имеют эффекта.
Финализаторы не могут уступать управление (yield) и запускать сборщик мусора. Поскольку они могут выполняться в непредсказуемые моменты, хорошей практикой является ограничение каждого финализатора минимумом, необходимым для правильного освобождения связанного с ним ресурса.
Любая ошибка при выполнении финализатора генерирует предупреждение; ошибка не распространяется.
2.5.4 – Слабые таблицы
Слабая таблица — это таблица, элементы которой являются слабыми ссылками. Слабая ссылка игнорируется сборщиком мусора. Иными словами, если единственные ссылки на объект являются слабыми ссылками, то сборщик мусора соберёт этот объект.
Слабая таблица может иметь слабые ключи, слабые значения или и то, и другое.
Таблица со слабыми значениями позволяет собирать её значения,
но предотвращает сборку её ключей.
Таблица как со слабыми ключами, так и со слабыми значениями позволяет собирать
и ключи, и значения.
В любом случае, если собирается либо ключ, либо значение,
вся пара удаляется из таблицы.
Слабость таблицы управляется
полем __mode её метатаблицы.
Это метазначение, если оно присутствует, должно быть одной из следующих строк:
"k" — для таблицы со слабыми ключами;
"v" — для таблицы со слабыми значениями;
или "kv" — для таблицы как со слабыми ключами, так и значениями.
Таблица со слабыми ключами и сильными значениями также называется таблицей эфемеронов (ephemeron table). В таблице эфемеронов значение считается достижимым только в том случае, если достижим его ключ. В частности, если единственная ссылка на ключ идёт через его значение, пара удаляется.
Любое изменение слабости таблицы может вступить в силу только в следующем цикле сборки. В частности, если вы измените слабость на более сильный режим, Lua всё ещё может собрать некоторые элементы из этой таблицы до того, как изменение вступит в силу.
Из слабых таблиц удаляются только объекты, имеющие явную конструкцию. Значения, такие как числа и лёгкие функции C , не подлежат сборке мусора и поэтому не удаляются из слабых таблиц (если только связанные с ними значения не собраны). Хотя строки подлежат сборке мусора, они не имеют явной конструкции, и их равенство определяется по значению; они ведут себя скорее как значения, чем как объекты. Поэтому они не удаляются из слабых таблиц.
Воскрешённые объекты (то есть объекты, которые финализируются, и объекты, доступные только через финализируемые объекты) имеют особое поведение в слабых таблицах. Они удаляются из слабых значений перед запуском их финализаторов, но удаляются из слабых ключей только в следующей сборке после запуска их финализаторов, когда такие объекты фактически освобождаются. Такое поведение позволяет финализатору получать доступ к свойствам, связанным с объектом, через слабые таблицы.
Если слабая таблица находится среди воскрешённых объектов в цикле сборки, она может быть неправильно очищена до следующего цикла.
2.6 – Сопрограммы
Lua поддерживает сопрограммы, также называемые кооперативной многопоточностью. Сопрограмма в Lua представляет независимый поток выполнения. Однако, в отличие от потоков в многопоточных системах, сопрограмма приостанавливает своё выполнение только путём явного вызова функции yield.
Вы создаёте сопрограмму, вызывая coroutine.create.
Её единственный аргумент — функция,
которая является главной функцией сопрограммы.
Функция create только создаёт новую сопрограмму и
возвращает дескриптор на неё (объект типа thread);
она не запускает сопрограмму.
Вы выполняете сопрограмму, вызывая coroutine.resume.
Когда вы впервые вызываете coroutine.resume,
передавая в качестве первого аргумента
поток, возвращённый coroutine.create,
сопрограмма начинает своё выполнение с
вызова своей главной функции.
Дополнительные аргументы, переданные в coroutine.resume, передаются
в качестве аргументов этой функции.
После того как сопрограмма начинает выполняться,
она выполняется, пока не завершится или не уступит управление (yield).
Сопрограмма может завершить своё выполнение двумя способами:
нормально, когда её главная функция возвращает управление
(явно или неявно, после последней инструкции);
и аварийно, если происходит незащищённая ошибка.
В случае нормального завершения
coroutine.resume возвращает true
плюс любые значения, возвращённые главной функцией сопрограммы.
В случае ошибок coroutine.resume возвращает false
плюс объект ошибки.
В этом случае сопрограмма не разматывает свой стек,
поэтому его можно исследовать после ошибки
с помощью API отладки.
Сопрограмма уступает управление, вызывая 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.close).
В качестве примера того, как работают сопрограммы, рассмотрим следующий код:
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.||DSML|| parameter>