Rust Basics Series #2: Използване на променливи и константи

Сподели

В първата глава от поредицата споделих мислите си защо Rust е все по-популярен език за програмиране. Също така показах как се пише програма Hello World в Rust.

Нека продължим това пътуване с Rust. В тази статия ще ви запозная с променливите и константите в езика за програмиране Rust.

Освен това ще разгледам и нова концепция за програмиране, наречена “засенчване”.

Уникалността на променливите на Rust

Променлива в контекста на език за програмиране (като Rust) е известна като псевдоним на адреса на паметта, в който се съхраняват някои данни.

Това важи и за езика за програмиране Rust. Но Rust има една уникална „функция“. Всяка променлива, която декларирате, е неизменен по подразбиране. Това означава, че след присвояване на стойност на променливата, тя не може да бъде променена.

Това решение беше взето, за да се гарантира, че по подразбиране не е необходимо да правите специални разпоредби като въртящи се ключалки или мутекси за въвеждане на многопоточност. Ръжда гаранции безопасна едновременност. Тъй като всички променливи (по подразбиране) са неизменни, не е нужно да се притеснявате за нишка, която променя стойност несъзнателно.

Това не означава, че променливите в Rust са като константи, защото не са. Променливите могат да бъдат изрично дефинирани, за да позволят мутация. Такава променлива се нарича a променлива променлива.

Следва синтаксисът за деклариране на променлива в Rust:

// immutability by default
// the initialized value is the **only** value
let variable_name = value;

// mutable variable defined by the use of 'mut' keyword
// the initial value can be changed to something else
let mut variable_name = value;

🚧

Въпреки че имате право да промените стойността на променлива променлива, не можете да й присвоите стойност на друг тип данни.

Което означава, че ако имате променлива променлива от тип float, не можете да й присвоите знак по-нататък.

Преглед на високо ниво на типовете данни на Rust

В предишната статия може би сте забелязали, че споменах, че Rust е строго въведен език. Но за да дефинирате променлива, вие не указвате типа данни, вместо това използвате обща ключова дума let.

Компилаторът на Rust може да изведе типа данни на променлива въз основа на присвоената й стойност. Но може да се направи, ако все пак искате да сте изрични с типовете данни и искате да анотирате типа. Следва синтаксисът:

let variable_name: data_type = value;

Някои от често срещаните типове данни в езика за програмиране Rust са както следва:

  • Целочислен тип: i32 и u32 за цели числа със знак и без знак, съответно 32-битови
  • Тип с плаваща запетая: f32 и f6432-битови и 64-битови числа с плаваща запетая
  • Булев тип: bool
  • Тип символ: char

Ще разгледам типовете данни на Rust по-подробно в следващата статия. Засега това трябва да е достатъчно.

🚧

Rust няма имплицитно преобразуване на типове. Така че, ако зададете стойността 8 към променлива с тип данни с плаваща запетая, ще се сблъскате с грешка при компилиране. Вместо това трябва да присвоите стойността 8. или 8.0.

Rust също налага променливата да бъде инициализирана, преди стойността, съхранена в нея, да бъде прочетена.

{ // this block won't compile
    let a;
    println!("{}", a); // error on this line
    // reading the value of an **uninitialized** variable is a compile-time error
}

{ // this block will compile
    let a;
    a = 128;
    println!("{}", a); // no error here
    // variable 'a' has an initial value
}

Ако декларирате променлива без начална стойност и я използвате, преди да й присвоите някаква начална стойност, компилаторът Rust ще хвърли грешка във времето на компилиране.

Въпреки че грешките са досадни. В този случай компилаторът Rust ви принуждава да не правите една от много често срещаните грешки, които човек прави, когато пише код: неинициализирани променливи.

Съобщения за грешка на компилатора на Rust

Нека напишем няколко програми, където вие

  1. Разберете дизайна на Rust, като изпълнявате „нормални“ задачи, които всъщност са основна причина за проблеми, свързани с паметта
  2. Прочетете и разберете съобщенията за грешка/предупреждение на компилатора на Rust

Тестване на неизменността на променливата

Нека умишлено напишем програма, която се опитва да модифицира променлива променлива и да видим какво ще се случи след това.

fn main() {
    let mut a = 172;
    let b = 273;
    println!("a: {a}, b: {b}");

    a = 380;
    b = 420;
    println!("a: {}, b: {}", a, b);
}

Изглежда като проста програма досега до ред 4. Но на ред 7, променливата b–неизменна променлива–променя своята стойност.

Обърнете внимание на двата метода за отпечатване на стойностите на променливите в Rust. На ред 4 затворих променливите във фигурни скоби, така че стойностите им да бъдат отпечатани. На ред 8 оставям скобите празни и предоставям променливите като аргументи, C стил. И двата подхода са валидни. (С изключение на промяната на стойността на неизменната променлива, всичко в тази програма е правилно.)

Да компилираме! Вече знаете как да направите това, ако следвате предишната глава.

$ rustc main.rs
error[E0384]: cannot assign twice to immutable variable `b`
 --> main.rs:7:5
  |
3 |     let b = 273;
  |         -
  |         |
  |         first assignment to `b`
  |         help: consider making this binding mutable: `mut b`
...
7 |     b = 420;
  |     ^^^^^^^ cannot assign twice to immutable variable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0384`.

📋

Думата “свързване” се отнася до името на променливата. Това обаче е прекалено опростяване.

Това идеално демонстрира стабилната проверка на грешки и информативните съобщения за грешка на Rust. Първият ред прочита съобщението за грешка, което предотвратява компилирането на горния код:

error[E0384]: cannot assign twice to immutable variable b

Това означава, че компилаторът на Rust е забелязал, че се опитвам да присвоя отново нова стойност на променливата b но променливата b е неизменна променлива. Така че това причинява тази грешка.

Компилаторът дори идентифицира точните номера на реда и колоната, където е открита тази грешка.

Под реда, който казва first assignment to `b` е линията, която предоставя помощ. Тъй като променям стойността на неизменната променлива bми се казва да декларирам променливата b като променлива променлива с помощта на mut ключова дума.

🖥️

Приложете корекция сами, за да разберете по-добре проблема.

Игра с неинициализирани променливи

Сега нека да разгледаме какво прави компилаторът на Rust, когато се чете стойност на неинициализирана променлива.

fn main() {
    let a: i32;
    a = 123;
    println!("a: {a}");

    let b: i32;
    println!("b: {b}");
    b = 123;
}

Тук имам две неизменни променливи a и b и двете са неинициализирани по време на декларирането. Променливата a получава присвоена стойност, преди стойността й да бъде прочетена. Но променливата bСтойността на се чете, преди да й бъде присвоена първоначална стойност.

Нека компилираме и видим резултата.

$ rustc main.rs
warning: value assigned to `b` is never read
 --> main.rs:8:5
  |
8 |     b = 123;
  |     ^
  |
  = help: maybe it is overwritten before being read?
  = note: `#[warn(unused_assignments)]` on by default

error[E0381]: used binding `b` is possibly-uninitialized
 --> main.rs:7:19
  |
6 |     let b: i32;
  |         - binding declared here but left uninitialized
7 |     println!("b: {b}");
  |                   ^ `b` used here but it is possibly-uninitialized
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0381`.

Тук компилаторът Rust извежда грешка във времето на компилиране и предупреждение. Предупреждението казва, че променливата bстойността на никога не се чете.

Но това е абсурдно! Стойността на променливата b се осъществява достъп на линия 7. Но погледнете внимателно; предупреждението се отнася до ред 8. Това е объркващо; нека временно пропуснем това предупреждение и да преминем към грешката.

Съобщението за грешка гласи това used binding `b` is possibly-uninitialized. Както в предишния пример, компилаторът Rust посочва, че грешката е причинена от четене на стойността на променливата b на ред 7. Причината, поради която се чете стойността на променливата b е грешка, че стойността му е неинициализирана. В езика за програмиране Rust това е незаконно. Оттук и грешката във времето на компилиране.

🖥️

Тази грешка може лесно да бъде разрешена чрез размяна на кодовете на редове 7 и 8. Направете го и вижте дали грешката изчезва.

Примерна програма: Размяна на номера

След като вече сте запознати с често срещаните проблеми, свързани с променливите, нека да разгледаме програма, която разменя стойностите на две променливи.

fn main() {
    let mut a = 7186932;
    let mut b = 1276561;

    println!("a: {a}, b: {b}");

    // swap the values
    let temp = a;
    a = b;
    b = temp;

    println!("a: {}, b: {}", a, b);
}

Тук съм декларирал две променливи, a и b. И двете променливи са променливи, защото искам да променя стойностите им в бъдеще. Зададох някои произволни стойности. Първоначално отпечатвам стойностите на тези променливи.

След това, на ред 8, създавам неизменна променлива, наречена temp и му присвоете стойността, съхранена в a. Причината тази променлива да е неизменна е, защото tempСтойността на няма да бъде променена.

За да разменя стойности, присвоявам стойността на променлива b към променлива a и на следващия ред присвоявам стойността на temp (който съдържа стойност на a) към променлива b. Сега, когато стойностите са разменени, отпечатвам стойности на променливи a и b.

Когато горният код се компилира и изпълни, получавам следния изход:

a: 7186932, b: 1276561
a: 1276561, b: 7186932

Както можете да видите, стойностите са разменени. перфектен

Използване на неизползвани променливи

Когато сте декларирали някои променливи, които възнамерявате да използвате по линията, но все още не сте ги използвали, и компилирате вашия Rust код, за да проверите нещо, Rust компилаторът ще ви предупреди за това.

Причината за това е очевидна. Променливите, които няма да бъдат използвани, заемат ненужно време за инициализация (цикъл на процесора) и място в паметта. Ако няма да се използва, защо го има в програмата си на първо място?

Но понякога може да сте в ситуация, в която създаването на променлива може да не е във вашите ръце. Да кажем, когато функция връща повече от една стойност и имате нужда само от няколко стойности. В този случай не можете да кажете на поддържащия библиотеката да коригира функцията си според вашите нужди.

Така че в такива моменти можете да имате променлива, която започва с долна черта и компилаторът на Rust вече няма да ви дава такива предупреждения. И ако наистина не е нужно дори да използвате стойността, съхранена в споменатата неизползвана променлива, можете просто да я наименувате _ (долна черта) и компилаторът на Rust също ще го игнорира!

Следната програма не само няма да генерира никакъв изход, но също така няма да генерира никакви предупреждения и/или съобщения за грешка:

fn main() {
    let _unnecessary_var = 0; // no warnings
    let _ = 0.0; // ignored completely
}

Аритметични операции

Тъй като математиката си е математика, Rust не прави иновации в нея. Можете да използвате всички аритметични оператори, които може да сте използвали в други езици за програмиране като C, C++ и/или Java.

Пълен списък на всички операции в езика за програмиране Rust, заедно с тяхното значение, можете да намерите тук.

Примерна програма: Ръждясал термометър

Следва типична програма, която преобразува Фаренхайт в Целзий и обратно.

fn main() {
    let boiling_water_f: f64 = 212.0;
    let frozen_water_c: f64 = 0.0;

    let boiling_water_c = (boiling_water_f - 32.0) * (5.0 / 9.0);
    let frozen_water_f = (frozen_water_c * (9.0 / 5.0)) + 32.0;

    println!(
        "Water starts boiling at {}°C (or {}°F).",
        boiling_water_c, boiling_water_f
    );
    println!(
        "Water starts freezing at {}°C (or {}°F).",
        frozen_water_c, frozen_water_f
    );
}

Тук не се случва много… Температурата по Фаренхайт се преобразува в Целзий и обратно за температурата в Целзий.

Както можете да видите тук, тъй като Rust не позволява автоматично преобразуване на типове, трябваше да въведа десетична точка към целите числа 32, 9 и 5. Освен това, това е подобно на това, което бихте направили в C, C++ и/ или Java.

Като учебно упражнение опитайте да напишете програма, която открива колко цифри има в дадено число.

Константи

С известни познания по програмиране може да знаете какво означава това. Константата е специален тип променлива, чиято стойност никога не се променя. Остава постоянно.

В езика за програмиране Rust константата се декларира чрез следния синтаксис:

const CONSTANT_NAME: data_type = value;

Както можете да видите, синтаксисът за деклариране на константа е много подобен на това, което видяхме при декларирането на променлива в Rust. Все пак има две разлики:

  1. Трябва да има постоянно име SCREAMING_SNAKE_CASE. Всички главни букви и думи, разделени с долна буква.
  2. Анотирането на типа данни на константата е необходимо.

Променливи срещу константи

Може би се чудите, след като променливите са неизменни по подразбиране, защо езикът ще включва и константи?

Следващата таблица трябва да ви помогне да разсеете съмненията си. (Ако сте любопитни и искате да разберете по-добре тези разлики, можете да погледнете моя блог, който показва тези разлики в детайли.)

Таблица, която показва разликите между променливи и константи в езика за програмиране Rust

Примерна програма, използваща константи: Изчислете площта на кръга

Следва ясна програма за константите в Rust. Той изчислява площта и периметъра на кръг.

fn main() {
    const PI: f64 = 3.14;
    let radius: f64 = 50.0;

    let circle_area = PI * (radius * radius);
    let circle_perimeter = 2.0 * PI * radius;

    println!("There is a circle with the radius of {radius} centimetres.");
    println!("Its area is {} centimetre square.", circle_area);
    println!(
        "And it has circumference of {} centimetres.",
        circle_perimeter
    );
}

И при стартиране на кода се получава следният изход:

There is a circle with the radius of 50 centimetres.
Its area is 7850 centimetre square.
And it has circumference of 314 centimetres.

Променливо засенчване в Rust

Ако сте програмист на C++, вече знаете какво имам предвид. Когато програмистът заявява нова променлива със същото име като вече декларирана променлива, тя е известна като засенчване на променлива.

За разлика от C++, Rust ви позволява да извършвате засенчване на променливи в същия обхват!

💡

Когато програмист засенчва съществуваща променлива, на новата променлива се присвоява нов адрес на паметта, но се посочва със същото име като съществуващата променлива.

Нека да разгледаме как работи в Rust.

fn main() {
    let a = 108;
    println!("addr of a: {:p}, value of a: {a}", &a);
    let a = 56;
    println!("addr of a: {:p}, value of a: {a} // post shadowing", &a);

    let mut b = 82;
    println!("naddr of b: {:p}, value of b: {b}", &b);
    let mut b = 120;
    println!("addr of b: {:p}, value of b: {b} // post shadowing", &b);

    let mut c = 18;
    println!("naddr of c: {:p}, value of c: {c}", &b);
    c = 29;
    println!("addr of c: {:p}, value of c: {c} // post shadowing", &b);
}

The :p във къдрави скоби в println твърдението е подобно на използването %p в C. Той указва, че стойността е във формат на адрес на паметта (указател).

Тук вземам 3 променливи. Променлива a е неизменна и е засенчена на ред 4. Променлива b е променлив и също е засенчен на ред 9. Променлива c е променлив, но на ред 14, само неговата стойност е мутирала. Не е засенчен.

Сега нека да разгледаме изхода.

addr of a: 0x7ffe954bf614, value of a: 108
addr of a: 0x7ffe954bf674, value of a: 56 // post shadowing

addr of b: 0x7ffe954bf6d4, value of b: 82
addr of b: 0x7ffe954bf734, value of b: 120 // post shadowing

addr of c: 0x7ffe954bf734, value of c: 18
addr of c: 0x7ffe954bf734, value of c: 29 // post shadowing

Разглеждайки изхода, можете да видите, че не само стойностите на трите променливи са се променили, но и адресите на променливите, които са били засенчени, също са различни (проверете последните няколко шестнадесетични знака).

Адресът на паметта за променливите a и b променен. Това означава, че променливостта или липсата на такава на променлива не е ограничение при засенчване на променлива.

Заключение

Тази статия обхваща променливи и константи в езика за програмиране Rust. Обхванати са и аритметичните операции.

Като обобщение:

  • Променливите в Rust са неизменни по подразбиране, но може да бъде въведена променливост.
  • Програмистът трябва изрично да посочи променливостта на променливата.
  • Константите винаги са неизменни, независимо какво и изискват анотация на типа.
  • Променливата засенчване декларира a нов променлива със същото име като съществуваща променлива.

Страхотно! Вярвам, че върви добре с Rust. В следващата глава ще обсъдя типовете данни в Rust. Останете на линия.

Междувременно, ако имате някакви въпроси, моля, уведомете ме.

Източник: itsfoss.com

Публикациите се превеждат автоматично с google translate

Loading


Сподели