Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Переменные и изменяемость

Как мы уже говорили в разделе «Храним значения в переменных», по умолчанию переменные неизменяемые. Так 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

Этот пример показывает, как компилятор помогает находить ошибки в программах. Ошибки компиляции бывают неприятны, но означают они лишь одно: программа пока не может безопасно сделать то, что вы задумали. Они вовсе не означают, что вы плохой программист! Даже опытные растацеаны постоянно получают ошибки компиляции.

Сообщение 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

Благодаря 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.

Константа доступна всё время, пока работает программа, — в той области видимости, где она объявлена. Поэтому константы удобны для значений из предметной области приложения, которые нужны разным частям программы: скажем, максимальное число очков, которое может набрать игрок, или скорость света.

Если значение зашито в код и используется по всей программе, лучше объявить его константой с понятным именем: тем, кто будет читать код после вас, будет проще понять, что это за число. Кроме того, если когда-нибудь его понадобится поменять, это придётся сделать лишь в одном месте.

Затенение

Как вы видели в игре «Угадай число» в главе 2, можно объявить новую переменную с тем же именем, что и у предыдущей. Растацеаны говорят, что вторая переменная затеняет (shadows) первую. Это значит, что, когда вы используете это имя, компилятор видит вторую переменную. По сути, вторая переменная заслоняет первую и забирает все обращения к этому имени себе — пока её саму не затенят или пока не закончится область видимости. Чтобы затенить переменную, достаточно ещё раз написать 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. Затем повторное let x = создаёт новую переменную 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_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

Теперь, когда мы разобрались, как работают переменные, посмотрим, какие типы данных у них бывают.