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

Zmienne i mutowalność

Jak wspomniano w sekcji „Przechowywanie wartości w zmiennych”, zmienne są domyślnie niezmienne. Jest to jedna z wielu wskazówek, które Rust daje Ci, abyś pisał swój kod w sposób, który wykorzystuje bezpieczeństwo i łatwą współbieżność, jakie oferuje Rust. Jednak nadal masz możliwość uczynienia swoich zmiennych mutowalnymi. Przyjrzyjmy się, jak i dlaczego Rust zachęca do preferowania niezmienności i dlaczego czasami możesz chcieć zrezygnować z tej opcji.

Kiedy zmienna jest niezmienna, po powiązaniu wartości z nazwą nie można zmienić tej wartości. Aby to zilustrować, wygeneruj nowy projekt o nazwie variables w swoim katalogu projects za pomocą cargo new variables.

Następnie, w nowym katalogu variables, otwórz src/main.rs i zastąp jego kod następującym kodem, który jeszcze się nie skompiluje:

Nazwa pliku: src/main.rs

fn main() {
    let x = 5;
    println!("Wartość x to: {x}");
    x = 6;
    println!("Wartość x to: {x}");
}

Zapisz i uruchom program za pomocą cargo run. Powinieneś otrzymać komunikat o błędzie dotyczący błędu niezmienności, jak pokazano w tym wyjściu:

$ 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

Ten przykład pokazuje, jak kompilator pomaga znaleźć błędy w Twoich programach. Błędy kompilatora mogą być frustrujące, ale tak naprawdę oznaczają tylko, że Twój program nie działa jeszcze bezpiecznie tak, jak chcesz; nie oznaczają, że nie jesteś dobrym programistą! Doświadczeni Rustaceanie nadal otrzymują błędy kompilatora.

Otrzymałeś komunikat o błędzie cannot assign twice to immutable variable `x` (nie można przypisać dwa razy do niezmiennej zmiennej x), ponieważ próbowałeś przypisać drugą wartość do niezmiennej zmiennej x.

Ważne jest, abyśmy otrzymywali błędy kompilacji, gdy próbujemy zmienić wartość, która została oznaczona jako niezmienna, ponieważ ta właśnie sytuacja może prowadzić do błędów. Jeśli jedna część naszego kodu działa w oparciu o założenie, że wartość nigdy się nie zmieni, a inna część naszego kodu zmienia tę wartość, możliwe jest, że pierwsza część kodu nie wykona tego, do czego została zaprojektowana. Przyczynę tego rodzaju błędu może być trudno wyśledzić po fakcie, zwłaszcza gdy druga część kodu zmienia wartość tylko czasami. Kompilator Rusta gwarantuje, że gdy zadeklarujesz, że wartość się nie zmieni, to naprawdę się nie zmieni, więc nie musisz sam tego śledzić. Twój kod jest dzięki temu łatwiejszy do przemyślenia.

Ale mutowalność może być bardzo przydatna i może sprawić, że kod będzie wygodniejszy w pisaniu. Chociaż zmienne są domyślnie niezmienne, możesz uczynić je mutowalnymi, dodając mut przed nazwą zmiennej, tak jak to zrobiłeś w Rozdziale 2. Dodanie mut również przekazuje intencję przyszłym czytelnikom kodu, wskazując, że inne części kodu będą zmieniać wartość tej zmiennej.

Na przykład, zmieńmy src/main.rs na następujący kod:

Nazwa pliku: src/main.rs

fn main() {
    let mut x = 5;
    println!("Wartość x to: {x}");
    x = 6;
    println!("Wartość x to: {x}");
}

Kiedy uruchomimy program teraz, otrzymamy:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
Wartość x to: 5
Wartość x to: 6

Możemy zmienić wartość przypisaną do x z 5 na 6, gdy użyto mut. Ostatecznie, decyzja o użyciu mutowalności zależy od Ciebie i od tego, co uważasz za najbardziej przejrzyste w danej sytuacji.

Deklarowanie stałych

Podobnie jak niezmienne zmienne, stałe to wartości, które są powiązane z nazwą i nie mogą być zmieniane, ale istnieją pewne różnice między stałymi a zmiennymi.

Po pierwsze, nie wolno używać mut ze stałymi. Stałe są nie tylko domyślnie niezmienne — są zawsze niezmienne. Stałe deklaruje się za pomocą słowa kluczowego const zamiast słowa kluczowego let, a typ wartości musi być annotowany. Typy i adnotacje typów omówimy w następnej sekcji, „Typy danych”, więc na razie nie martw się szczegółami. Wystarczy wiedzieć, że zawsze musisz podawać typ.

Stałe mogą być deklarowane w dowolnym zasięgu, w tym w zasięgu globalnym, co czyni je użytecznymi dla wartości, o których wiele części kodu musi wiedzieć.

Ostatnia różnica polega na tym, że stałe mogą być ustawiane tylko na wyrażenie stałe, a nie na wynik wartości, która mogłaby być obliczona tylko w czasie uruchamiania.

Oto przykład deklaracji stałej:

#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}

Nazwa stałej to THREE_HOURS_IN_SECONDS, a jej wartość jest ustawiona na wynik pomnożenia 60 (liczby sekund w minucie) przez 60 (liczby minut w godzinie) przez 3 (liczby godzin, które chcemy policzyć w tym programie). Konwencją nazewniczą Rusta dla stałych jest używanie samych dużych liter z podkreśleniami między słowami. Kompilator jest w stanie ocenić ograniczony zestaw operacji w czasie kompilacji, co pozwala nam zapisać tę wartość w sposób łatwiejszy do zrozumienia i weryfikacji, zamiast ustawiać tę stałą na wartość 10 800. Więcej informacji na temat operacji, które można stosować przy deklarowaniu stałych, znajdziesz w sekcji Rust Reference dotyczącej ewaluacji stałych.

Stałe są ważne przez cały czas działania programu, w zasięgu, w którym zostały zadeklarowane. Ta właściwość sprawia, że stałe są przydatne dla wartości w domenie aplikacji, o których wiele części programu może potrzebować wiedzieć, takich jak maksymalna liczba punktów, jaką każdy gracz może zdobyć, lub prędkość światła.

Nazywanie zakodowanych wartości używanych w całym programie jako stałych jest przydatne w przekazywaniu znaczenia tej wartości przyszłym utrzymującym kod. Pomaga również mieć tylko jedno miejsce w kodzie, które trzeba by zmienić, gdyby zakodowana wartość wymagała aktualizacji w przyszłości.

Przesłanianie

Jak widziałeś w samouczku gry w zgadywanie w Rozdziale 2, możesz zadeklarować nową zmienną o tej samej nazwie co poprzednia zmienna. Rustaceanie mówią, że pierwsza zmienna jest przesłonięta przez drugą, co oznacza, że druga zmienna jest tym, co kompilator będzie widział, gdy użyjesz nazwy zmiennej. W efekcie druga zmienna przysłania pierwszą, przejmując wszelkie użycia nazwy zmiennej na siebie, dopóki sama nie zostanie przesłonięta lub zasięg się nie zakończy. Możemy przesłonić zmienną, używając tej samej nazwy zmiennej i powtarzając użycie słowa kluczowego let w następujący sposób:

Nazwa pliku: src/main.rs

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("Wartość x w wewnętrznym zasięgu to: {x}");
    }

    println!("Wartość x to: {x}");
}

Ten program najpierw wiąże x z wartością 5. Następnie tworzy nową zmienną x, powtarzając let x =, biorąc oryginalną wartość i dodając 1 tak, aby wartość x wynosiła 6. Następnie, wewnątrz wewnętrznego zasięgu utworzonego za pomocą nawiasów klamrowych, trzecia instrukcja let również przesłania x i tworzy nową zmienną, mnożąc poprzednią wartość przez 2, aby nadać x wartość 12. Gdy ten zasięg się kończy, wewnętrzne przesłanianie zakończy się, a x powróci do 6. Kiedy uruchomimy ten program, wyświetli się następujący wynik:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
Wartość x w wewnętrznym zasięgu to: 12
Wartość x to: 6

Przesłanianie różni się od oznaczania zmiennej jako mut, ponieważ otrzymamy błąd kompilacji, jeśli przypadkowo spróbujemy przypisać nową wartość do tej zmiennej bez użycia słowa kluczowego let. Używając let, możemy wykonać kilka transformacji na wartości, ale po tych transformacjach zmienna pozostanie niezmienna.

Inna różnica między mut a przesłanianiem polega na tym, że ponieważ efektywnie tworzymy nową zmienną, gdy ponownie używamy słowa kluczowego let, możemy zmienić typ wartości, ale ponownie użyć tej samej nazwy. Na przykład, powiedzmy, że nasz program prosi użytkownika o podanie liczby spacji, jakie chce między tekstem, wprowadzając spacje, a następnie chcemy przechowywać te dane wejściowe jako liczbę:

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
}

Pierwsza zmienna spaces jest typu string, a druga zmienna spaces jest typu liczbowego. Przesłanianie pozwala nam uniknąć wymyślania różnych nazw, takich jak spaces_str i spaces_num; zamiast tego możemy ponownie użyć prostszej nazwy spaces. Jednakże, jeśli spróbujemy użyć mut w tym przypadku, jak pokazano tutaj, otrzymamy błąd kompilacji:

fn main() {
    let mut spaces = "   ";
    spaces = spaces.len();
}

Błąd mówi, że nie wolno nam mutować typu zmiennej:

$ 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

Teraz, gdy zbadaliśmy, jak działają zmienne, przyjrzyjmy się innym typom danych, które mogą mieć.