Привязка даёт значению имя. В месте объявления сразу решаются ещё два вопроса: можно ли позднее присвоить имени новое значение и в какой части программы оно видно. Замыкание способно сохранить нужную привязку даже после завершения окружающей функции.
Проследите за изменением захваченной привязки
Сохраните этот код в файле bindings-scope-closures.tpz:
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)Проверьте и запустите программу:
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 и ресурсы.
Первые неизменяемые и изменяемые привязки можно повторить в уроке Значения и функции. Как выбрать между именованной функцией, лямбдой и обобщённой функцией, объясняет страница Функции и обобщения.