Переменные и изменяемость
Как упоминалось в разделе «Хранение значений с помощью переменных», по умолчанию переменные являются неизменяемыми. Это одно из многих побуждений Rust, которые помогают вам писать код, использующий преимущества безопасности и лёгкой конкурентности, которые предлагает Rust. Однако у вас всё ещё есть возможность сделать переменные изменяемыми. Давайте рассмотрим, как и почему Rust поощряет отдавать предпочтение неизменяемости и почему иногда вы можете захотеть от неё отказаться.
Когда переменная является неизменяемой, после связывания значения с именем вы не можете изменить это значение. Чтобы это проиллюстрировать, создайте новый проект с именем variables в вашем каталоге projects с помощью cargo new variables.
Затем в новом каталоге variables откройте файл src/main.rs и замените его код следующим кодом, который пока не скомпилируется:
Имя файла: src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
Сохраните и запустите программу с помощью cargo run. Вы должны получить сообщение об ошибке, связанной с неизменяемостью, как показано в этом выводе:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
Этот пример показывает, как компилятор помогает находить ошибки в ваших программах. Ошибки компилятора могут расстраивать, но на самом деле они означают лишь то, что ваша программа пока не делает безопасно то, что вы хотите; они не означают, что вы плохой программист! Опытные разработчики на Rust тоже получают ошибки компиляции.
Вы получили сообщение об ошибке cannot assign twice to immutable variable `x` (нельзя дважды присваивать значение неизменяемой переменной x), потому что попытались присвоить второе значение неизменяемой переменной x.
Важно получать ошибки на этапе компиляции, когда мы пытаемся изменить значение, которое обозначено как неизменяемое, потому что именно такая ситуация может привести к ошибкам. Если одна часть нашего кода работает на предположении, что значение никогда не изменится, а другая часть кода изменяет это значение, возможно, что первая часть кода не будет делать то, для чего она была разработана. Причина такого рода ошибок может быть трудна для отслеживания постфактум, особенно когда второй фрагмент кода изменяет значение только иногда. Компилятор Rust гарантирует, что когда вы заявляете, что значение не изменится, оно действительно не изменится, поэтому вам не нужно следить за этим самостоятельно. Таким образом, ваш код легче анализировать.
Но изменяемость может быть очень полезна и может сделать код более удобным для написания. Хотя переменные по умолчанию неизменяемы, вы можете сделать их изменяемыми, добавив mut перед именем переменной, как вы это делали в Главе 2. Добавление mut также передаёт намерение будущим читателям кода, указывая, что другие части кода будут изменять значение этой переменной.
Например, давайте изменим src/main.rs на следующий код:
Имя файла: src/main.rs
fn main() { let mut x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }
Когда мы теперь запускаем программу, мы получаем:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
Нам разрешено изменить значение, связанное с x, с 5 на 6, когда используется mut. В конечном счёте, решение об использовании изменяемости или нет остаётся за вами и зависит от того, что вы считаете наиболее понятным в данной конкретной ситуации.
Константы
Как и неизменяемые переменные, константы — это значения, связанные с именем, и они не могут изменяться, но между константами и переменными есть несколько различий.
Во-первых, вам не разрешено использовать mut с константами. Константы не просто неизменяемы по умолчанию — они всегда неизменяемы. Вы объявляете константы с помощью ключевого слова const вместо let, и тип значения должен быть аннотирован. Мы рассмотрим типы и аннотации типов в следующем разделе, «Типы данных», так что не беспокойтесь о деталях прямо сейчас. Просто знайте, что вы всегда должны аннотировать тип.
Константы могут быть объявлены в любой области видимости, включая глобальную, что делает их полезными для значений, о которых должны знать многие части кода.
Последнее отличие заключается в том, что константам может быть присвоено только константное выражение, а не результат значения, который мог бы быть вычислен только во время выполнения.
Вот пример объявления константы:
#![allow(unused)] fn main() { const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; }
Имя константы — THREE_HOURS_IN_SECONDS, и её значение устанавливается как результат умножения 60 (количество секунд в минуте) на 60 (количество минут в часе) на 3 (количество часов, которые мы хотим подсчитать в этой программе). Соглашение об именах констант в Rust — использовать все заглавные буквы с подчёркиваниями между словами. Компилятор способен оценивать ограниченный набор операций на этапе компиляции, что позволяет нам выбрать способ записи этого значения, который будет легче понять и проверить, вместо того чтобы устанавливать эту константу в значение 10 800. См. раздел Справочника Rust о вычислении констант для получения дополнительной информации о том, какие операции можно использовать при объявлении констант.
Константы действительны в течение всего времени выполнения программы, в области видимости, в которой они были объявлены. Это свойство делает константы полезными для значений в вашей предметной области, о которых могут needing знать несколько частей программы, например, максимальное количество очков, которое любой игрок в игре может заработать, или скорость света.
Называние жёстко заданных значений, используемых по всей программе, константами полезно для передачи смысла этого значения будущим сопровождающим кода. Это также помогает иметь только одно место в вашем коде, которое нужно было бы изменить, если бы жёстко заданное значение потребовалось обновить в будущем.
Затенение
Как вы видели в учебнике по игре угадывания в Главе 2, вы можете объявить новую переменную с тем же именем, что и у предыдущей переменной. Разработчики на Rust говорят, что первая переменная затенена второй, что означает, что вторая переменная — та, которую увидит компилятор, когда вы используете имя переменной. По сути, вторая переменная затмевает первую, принимая на себя все использования имени переменной, пока либо она сама не будет затенена, либо не закончится область видимости. Мы можем затенять переменную, используя то же имя переменной и повторяя использование ключевого слова let, как показано ниже:
Имя файла: src/main.rs
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); } println!("The value of x is: {x}"); }
Эта программа сначала связывает x со значением 5. Затем она создаёт новую переменную x, повторяя let x =, беря исходное значение и добавляя 1, так что значение x становится 6. Затем, во внутренней области видимости, созданной с помощью фигурных скобок, третье предложение let также затеняет x и создаёт новую переменную, умножая предыдущее значение на 2, чтобы задать x значение 12. Когда эта область видимости заканчивается, внутреннее затенение завершается, и x возвращается к значению 6. Когда мы запускаем эту программу, она выведет следующее:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
Затенение отличается от пометки переменной как mut, потому что мы получим ошибку на этапе компиляции, если случайно попытаемся повторно присвоить значение этой переменной без использования ключевого слова let. Используя let, мы можем выполнить несколько преобразований над значением, но иметь переменную неизменяемой после завершения этих преобразований.
Другое различие между mut и затенением заключается в том, что поскольку мы фактически создаём новую переменную, когда снова используем ключевое слово let, мы можем изменить тип значения, но повторно использовать то же имя. Например, предположим, что наша программа спрашивает у пользователя, сколько пробелов он хочет между некоторым текстом, вводя символы пробела, а затем мы хотим сохранить этот ввод как число:
fn main() { let spaces = " "; let spaces = spaces.len(); }
Первая переменная spaces имеет строковый тип, а вторая переменная spaces имеет числовой тип. Таким образом, затенение избавляет нас от необходимости придумывать разные имена, такие как spaces_str и spaces_num; вместо этого мы можем повторно использовать более простое имя spaces. Однако если мы попытаемся использовать mut для этого, как показано здесь, мы получим ошибку на этапе компиляции:
fn main() {
let mut spaces = " ";
spaces = spaces.len();
}
Ошибка говорит, что нам не разрешено изменять тип переменной:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error
Теперь, когда мы рассмотрели, как работают переменные, давайте посмотрим на большее количество типов данных, которые они могут иметь.