Основы языка

Связывания, области и замыкания

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

Связывание присваивает значению имя. В месте объявления сразу решаются ещё два вопроса: можно ли позднее присвоить имени новое значение и в какой части программы оно видно. Замыкание способно сохранить нужное связывание даже после завершения окружающей функции.

Проследите за изменением захваченного связывания

Сохраните этот код в файле bindings-scope-closures.tpz:

TOPAZ
function makeCounter(start: int) -> () -> int {
    let mut current = start
    () => {
        current = current + 1
        current
    }
}

let label = "outer"
let next = makeCounter(10)

{
    let label = "inner"
    print("{label}: {next()}, {next()}")
}

print(label)

Проверьте и запустите программу:

BASH
topaz check bindings-scope-closures.tpz
topaz run bindings-scope-closures.tpz

Два вызова используют один счётчик, а внутреннее имя label исчезает вместе со своим блоком:

Вывод
inner: 11, 12
outer

Следите за связыванием, а не за копией значения

makeCounter создаёт изменяемое связывание current и возвращает лямбду. Лямбда обращается к current, поэтому захватывает это связывание. Поскольку исходное объявление использует let mut, присваивание внутри лямбды разрешено.

Замыкание не сохраняет неподвижную копию числа 10. Оно продолжает ссылаться на ту же изменяемую ячейку. Первый вызов записывает в неё 11, второй читает новое значение и записывает 12. После выхода из makeCounter ячейка остаётся живой, потому что next всё ещё ссылается на неё.

Имя label показывает работу лексической области видимости. Внутренний блок может затенить внешнее имя собственным связыванием. Внутри блока label означает "inner". После выхода из блока внутренняя область заканчивается, и снова видно внешнее значение "outer". Повторное объявление одного имени в одной области является статической ошибкой.

Выберите связывание осознанно

ФормаКогда применятьПовторное присваивание
constЗначение времени компиляции, составленное по ограниченным правилам константных выраженийЗапрещено
letОбычное значение времени выполнения, связывание которого не меняетсяЗапрещено
let mutСостояние, изменение которого является частью моделиРазрешено

Обычно начинайте с let. Добавляйте mut лишь тогда, когда последующее присваивание имеет смысл в модели. Так изменяемое состояние заметно уже в месте объявления, а не предполагается у каждого имени.

Параметры и имена, введённые образцом, неизменяемы. Если функции нужно изменяемое локальное состояние, сохраните значение параметра в новом связывании let mut и дайте ему имя, описывающее это состояние.

Частая ошибка: считать захват снимком

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

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

Точные ограничения

  • Внутренняя область может затенить внешнее имя, но одна область не может объявить одно имя дважды.
  • Нельзя присваивать новое значение неизменяемому let, const, параметру или связыванию образца.
  • Изменяемое объявление принимает один идентификатор, а не образец деструктурирования.
  • Явные списки захвата, захват с перемещением, разновидности замыканий Rust и синтаксис заимствующих получателей не поддерживаются.
  • Время жизни ресурса — отдельное решение. Форма using только для File и гарантированная очистка описаны на странице Defer и ресурсы.

Первые неизменяемые и изменяемые связывания можно повторить в уроке Значения и функции. Как выбрать между именованной функцией, лямбдой и обобщённой функцией, объясняет страница Функции и обобщения.