Переменные и изменяемость
Как упоминалось в разделе «Сохранение значений с помощью переменных», по умолчанию переменные неизменяемы. Это один из многих способов, которыми Rust подталкивает вас писать код так, чтобы использовать преимущества безопасности и простой конкурентности, предоставляемые Rust. Однако у вас всё ещё есть возможность сделать переменные изменяемыми. Давайте разберёмся, как и почему Rust поощряет отдавать предпочтение неизменяемости и почему иногда вы можете захотеть от неё отказаться.
Когда переменная неизменяема, после привязки значения к имени это значение
нельзя изменить. Чтобы показать это, создайте в каталоге projects новый
проект с именем variables, используя 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.
Важно получать ошибки времени компиляции, когда мы пытаемся изменить значение, помеченное как неизменяемое, потому что именно такая ситуация может приводить к ошибкам. Если одна часть нашего кода работает исходя из предположения, что значение никогда не изменится, а другая часть кода изменяет это значение, возможно, первая часть кода не сделает то, для чего она была написана. Причину такой ошибки может быть трудно отследить постфактум, особенно когда второй фрагмент кода изменяет значение только иногда. Компилятор 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
Когда используется mut, нам разрешено изменить значение, связанное с x,
с 5 на 6. В конечном счёте решение о том, использовать изменяемость или
нет, остаётся за вами и зависит от того, что вы считаете наиболее понятным
в конкретной ситуации.
Объявление констант
Как и неизменяемые переменные, константы — это значения, связанные с именем, которые нельзя изменять, но между константами и переменными есть несколько различий.
Во-первых, с константами нельзя использовать 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 Reference
о вычислении констант.
Константы действительны всё время работы программы в пределах той области видимости, в которой они были объявлены. Это свойство делает константы полезными для значений из предметной области вашего приложения, о которых может понадобиться знать нескольким частям программы, например максимальное количество очков, которое разрешено набрать любому игроку, или скорость света.
Именование жёстко заданных значений, используемых по всей программе, как констант помогает передать смысл этих значений будущим сопровождающим кода. Также полезно иметь только одно место в коде, которое нужно будет изменить, если жёстко заданное значение потребуется обновить в будущем.
Затенение
Как вы видели в руководстве по игре «Угадай число» в
Главе 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
Теперь, когда мы разобрались, как работают переменные, давайте рассмотрим другие типы данных, которые они могут иметь.