Уроки · 18
Где живёт переменная
В уроке «Изменяемые переменные» мы увидели: если x
объявить внутри loop, она создаётся заново каждый кадр и теряет
накопленное значение, а если до loop — живёт и копит значение всё
время игры. mut разрешает менять значение, пока переменная жива,
но не решает, сколько именно она живёт. Разберёмся, почему так — дело
в границах, которые задают фигурные скобки { }.
Область видимости
Заголовок раздела «Область видимости»Фигурные скобки { } — это и есть область видимости. Как только
выполнение доходит до закрывающей скобки, всё, что было объявлено
внутри, уничтожается. В следующий раз, когда этот блок выполнится
снова (например, на следующем проходе loop), переменные внутри
него создаются заново, с нуля, по своей инициализации — как будто в
первый раз.
Переменные снаружи блока при этом видны и доступны изнутри — в том числе из вложенных блоков ещё глубже. А вот переменные, объявленные внутри блока, снаружи него уже не существуют.
Ловушка: mut внутри loop
Заголовок раздела «Ловушка: mut внутри loop»use macroquad::prelude::*;
#[macroquad::main("Движение")]async fn main() { loop { clear_background(BLACK);
// x объявлена внутри loop — создаётся заново на каждом кадре. let mut x = screen_width() / 2.0;
// Стрелка вправо зажата — двигаем x. if is_key_down(KeyCode::Right) { x += 8.0; }
draw_circle(x, screen_height() / 2.0, 60.0, YELLOW);
next_frame().await; }}Зажми стрелку вправо — и кружок сдвинется на 8 пикселей от центра и
дальше замрёт на месте, сколько бы ни держал клавишу. x здесь
создаётся заново каждый кадр внутри loop, поэтому в конце кадра она
уничтожается вместе со своим значением — а на следующем кадре
появляется новая x, опять ровно по центру. mut тут не спасает: он
разрешает менять x, пока она жива, — то есть в пределах одного
кадра, — но к следующему кадру это уже совсем другая переменная.
Исправляем: выносим x за loop
Заголовок раздела «Исправляем: выносим x за loop»use macroquad::prelude::*;
#[macroquad::main("Движение")]async fn main() { // x объявлена до loop — создаётся один раз и живёт всё время игры. let mut x = screen_width() / 2.0;
loop { clear_background(BLACK);
// Стрелка вправо зажата — двигаем x. if is_key_down(KeyCode::Right) { x += 8.0; }
draw_circle(x, screen_height() / 2.0, 60.0, YELLOW);
next_frame().await; }}Теперь x создаётся один раз, ещё до первого прохода loop, и с тех
пор просто живёт. Каждый x += 8.0 прибавляется к той же самой x,
что была на прошлом кадре, — поэтому движение накапливается, и кружок
едет дальше и дальше, а не дёргается на 8 пикселей и замирает.
Что здесь происходит
Заголовок раздела «Что здесь происходит»{ }уloop— это и есть область видимости: всё, что объявлено прямо внутри неё, живёт один проход и уничтожается вместе с его концом.- Вложенный блок
if { ... }внутриloopвидитx, даже если она объявлена ещё доloop, — блок глубже всегда видит переменные снаружи себя. - Значит, чтобы переменная копила значение между кадрами (как
xздесь, илиx/yв уроке «Движение»), объявлять её нужно снаружиloop— тогда область её видимости шире одного кадра.