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