Змінні і мутабельність
Як уже згадувалося у підрозділі “Зберігання значень у змінних” , усталено змінні є немутабельними. Це - один з численних штурханців, якими 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
Цей приклад показує, як компілятор допомагає вам знаходити помилки у програмах. Хоча повідомлення компілятора про помилки й можуть засмучувати, та вони лише означають, що ваша програма ще не робить те, що ви хотіли, у безпечний спосіб; вони не означають, що ви поганий програміст! Досвідчені растацеанці також отримують повідомлення про помилки від компілятора.
The error message indicates that the cause of the error is that you cannot assign twice to immutable variable `x`
, because you tried to assign a second value to the immutable x
variable.
Важливо, що ми отримали помилку часу компіляції, коли намагалися змінити значення, яке раніше визначили як немутабельне, тому що ця ситуація може призвести до вад у програмі. Якщо одна частина нашого коду працює з припущенням, що значення не буде змінене, а інша частина нашого коду змінює це значення, можливо, що перша частина коду буде робити не те, для чого вона була розроблена. Цю причину вад важко відслідкувати після виявлення, особливо коли другий фрагмент коду змінює значення лише час від часу. У 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
, і тип значення має явно позначатися. Ми розкажемо про типи і анотації типів у наступному підрозділі, "Типи даних", тому не хвилюйтеся зараз про деталі. Просто пам'ятайте, що тип констант треба зазначати завжди.
Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.
The last difference is that constants may be set only to a constant expression, not the result of a value that could only be computed at runtime.
Ось приклад проголошення константи:
#![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
Now that we’ve explored how variables work, let’s look at more data types they can have. ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number