Змінні і Мутабельність

Як уже згадувалося у підрозділі “Зберігання значень у змінних” , за замовчанням змінні є немутабельними. Це - один з численних штурханців, якими 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`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` due to previous error

Цей приклад показує, як компілятор допомагає вам знаходити помилки у програмах. Хоча повідомлення компілятора про помилки й можуть засмучувати, та вони лише означають, що ваша програма ще не робить те, що ви хотіли, у безпечний спосіб; вони не означають, що ви поганий програміст! Досвідчені растацеанці також отримують повідомлення про помилки від компілятора.

Ви отримали повідомлення про помилку 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 [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 вимагає використання верхнього регістру із підкресленнями між словами. Компілятор здатний обчислити невеликий набір операцій під час компіляції, що дозволяє нам виписати це значення так, щоб його було легше зрозуміти та перевірити, замість того щоб встановлювати константі значення 10800. Зверніться до Підрозділу Довідника Rust про обчислення констант за додатковою інформацією про те, які операції можна використовувати при проголошенні констант.

Константи є коректними протягом усього часу життя програми у тій області видимості, де вони були проголошені. Це робить константи корисними для зберігання значень з предметної області вашого застосунку, про які необхідно знати багатьом частинам програми, наприклад, максимальна кількість балів, яку може отримати гравець чи швидкість світла.

Корисно давати назви жорстко заданим значенням, що використовуються у вашій програмі, позначаючи їх константами, щоб передати сенс цього значення тим, хто супроводжуватиме код. Це також корисно тим, що в коді буде тільки одне місце, яке буде необхідно змінити у разі потреби оновити жорстко задане значення.

Затінення

Як ви бачили під час програмування гри - відгадайки у Розділі 2, можна проголошувати нову змінну із таким самим іменем, як і в раніше проголошеної змінної. Растацеанці кажуть, що перша змінна затінена другою, що означає, що при використанні змінної компілятор бачить лише другу змінну. По суті, друга змінна перекриває першу, перехоплюючи будь-яку згадку імені змінної на себе до тих пір, поки вона сама не буде затінена або область видимості не закінчиться. Ми можемо затінити змінну за допомогою ключового слова 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 [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` due to previous error

Тепер, дослідивши, як працюють змінні, погляньмо, які типи данних вони можуть зберігати. ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number