Переменные и изменяемость
Как упоминалось в разделе “Хранение значений с помощью переменных” , по умолчанию, переменные неизменяемы. Это — одно из средств Rust, побуждающее вас писать код так, чтобы он использовал преимущества безопасности и лёгкого параллелизма, предоставляемые языком. Однако, вы всё же можете делать свои переменные изменяемыми. Давайте рассмотрим, как и почему Rust побуждает вас отдавать предпочтение неизменяемости данных, и почему вам иногда может захотеться отказаться от неё.
Если переменная неизменяема, то однажды присвоив ей значение, вы не сможете его поменять. Чтобы проиллюстрировать это, создадим в директории projects новый проект variables. Выполним для этого команду cargo new variables.
Затем, в вашей новой директории variables, откройте src/main.rs и замените его код тем, что ниже (пока что он не будет компилироваться):
Файл: src/main.rs
fn main() {
let x = 5;
println!("Значение x: {x}");
x = 6;
println!("Значение x: {x}");
}
Save and run the program using cargo run. You should receive an error message regarding an immutability error, as shown in this output:
$ 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!("Значение x: {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.
It’s important that we get compile-time errors when we attempt to change a value that’s designated as immutable, because this very situation can lead to bugs. If one part of our code operates on the assumption that a value will never change and another part of our code changes that value, it’s possible that the first part of the code won’t do what it was designed to do. The cause of this kind of bug can be difficult to track down after the fact, especially when the second piece of code changes the value only sometimes. The Rust compiler guarantees that when you state that a value won’t change, it really won’t change, so you don’t have to keep track of it yourself. Your code is thus easier to reason through.
But mutability can be very useful and can make code more convenient to write. Although variables are immutable by default, you can make them mutable by adding mut in front of the variable name as you did in Chapter 2. Adding mut also conveys intent to future readers of the code by indicating that other parts of the code will be changing this variable’s value.
Например, давайте поменяем src/main.rs вот так:
Файл: src/main.rs
fn main() {
let mut x = 5;
println!("Значение x: {x}");
x = 6;
println!("Значение x: {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`
Значение x: 5
Значение x: 6
Мы смогли поменять значение, связанное с x, с 5 на 6, поскольку использовали mut. В конечном счёте, вопрос об использовании изменяемости остаётся за вами и определяется тем, какое решение, по вашему мнению, будет чище.
Declaring Constants
Как и неизменяемые переменные, константы — это привязанные к имени неизменяемые значения. Однако, между константами и неизменяемыми переменными разница есть.
Во-первых, вы не можете использовать mut с константами. Константы не просто неизменяемы по умолчанию — они вообще неизменяемы, всегда. Константы объявляются с помощью ключевого слова const (вместо ключевого слова let), а также они должны быть аннотированы типом. Мы расскажем подробнее о типах и аннотациях типа в следующем разделе — “Типы данных”; так что пока что не задумывайтесь много о деталях. Просто помните, что вы обязаны аннотировать тип констант.
Константы могут быть объявлены в любой области видимости, в том числе и в глобальной, что делает их полезными для объявления значений, которые используются в программе повсеместно.
Последнее отличие констант от неизменяемых переменных состоит в том, что константы могут связываться только с константыми выражениями — то есть такими, которые могут быть вычислены на этапе компиляции.
Вот пример объявление константы:
#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}
The constant’s name is THREE_HOURS_IN_SECONDS, and its value is set to the result of multiplying 60 (the number of seconds in a minute) by 60 (the number of minutes in an hour) by 3 (the number of hours we want to count in this program). Rust’s naming convention for constants is to use all uppercase with underscores between words. The compiler is able to evaluate a limited set of operations at compile time, which lets us choose to write out this value in a way that’s easier to understand and verify, rather than setting this constant to the value 10,800. See the Rust Reference’s section on constant evaluation for more information on what operations can be used when declaring constants.
Константы существуют в памяти в течение всего времени исполнения программы — в той области видимости, в которой были объявлены. Это свойство делает константы полезными для определения значений в программе, многим частям которой необходимо знать, например, скорость света или о максимальном количестве очков, которые может получить игрок.
Naming hardcoded values used throughout your program as constants is useful in conveying the meaning of that value to future maintainers of the code. It also helps to have only one place in your code that you would need to change if the hardcoded value needed to be updated in the future.
Затенение
Как вы увидели в Главе 2 ,вы можете объявить новую переменную с тем же именем, которое носит уже существующая переменная. Считается, что старая переменная затеняется новой, что означает, что при использовании данного имени компилятор будет искать значение именно в новой переменной. Старая переменная не будет видна до тех пор, пока не закончится область видимости новой. Кроме того, затеняющая переменная сама может быть затенённой. Мы можем затенить переменную, использовав её же имя и повторно использовав его с ключевым словом let; вот так:
Файл: src/main.rs
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("Значение x во внутренней области видимости: {x}");
}
println!("Значение x: {x}");
}
This program first binds x to a value of 5. Then, it creates a new variable x by repeating let x =, taking the original value and adding 1 so that the value of x is 6. Then, within an inner scope created with the curly brackets, the third let statement also shadows x and creates a new variable, multiplying the previous value by 2 to give x a value of 12. When that scope is over, the inner shadowing ends and x returns to being 6. When we run this program, it will output the following:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
Значение x во внутренней области видимости: 12
Значение x: 6
Shadowing is different from marking a variable as mut because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have completed.
Другим отличием между mut и затенением является то, что поскольку мы, по сути, создаём новую переменную (поскольку используем ключевое слово let), мы можем поменять тип значения, но использовать то же самое имя для связанной с ним переменной. Например, допустим, наша программа спрашивает пользователя, скольким количеством пробелов пользователь хочет отбить некоторый текст, и просит для этого у пользователя непосредственно нужные несколько знаков пробела. Мы можем сохранить количество введённых пробелов в виде числа вот таким образом:
fn main() {
let spaces = " ";
let spaces = spaces.len();
}
The first spaces variable is a string type, and the second spaces variable is a number type. Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num; instead, we can reuse the simpler spaces name. However, if we try to use mut for this, as shown here, we’ll get a compile-time error:
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
Теперь мы знаем, как работают переменные. Давайте теперь подробнее посмотрим на типы данных, которые эти переменные могут хранить.