Lekcje modułu (1/2)
Zasady Ownership
System zarządzania własnością (Ownership) to najbardziej charakterystyczna cecha języka Rust. Pozwala on na zagwarantowanie bezpieczeństwa pamięci bez potrzeby używania odśmiecacza pamięci (Garbage Collector) oraz bez konieczności ręcznego zwalniania pamięci sterty (heap) jak w C lub C++.
Trzy reguły własności (Ownership)
Zarządzanie pamięcią w języku Rust opiera się na trzech prostych regułach rygorystycznie kontrolowanych przez kompilator:
- Każda wartość w języku Rust ma zmienną zwaną jej właścicielem (owner).
- W danym momencie może istnieć tylko jeden właściciel.
- Kiedy właściciel wychodzi poza zakres (scope), wartość jest automatycznie usuwana.
Stos (Stack) vs Sterta (Heap)
Aby zrozumieć mechanizm własności, ważne jest, aby wiedzieć, gdzie znajdują się dane w pamięci:
- Stos (Stack): przechowuje dane o stałym i znanym w momencie kompilacji rozmiarze (np. liczby całkowite, wartości logiczne). Dostęp do nich jest niezwykle szybki.
- Sterta (Heap): przechowuje dane o dynamicznym i nieznanym w momencie kompilacji rozmiarze (np.
String). Alokacja pamięci na stercie jest wolniejsza i wymaga zapisania wskaźnika (pointer) na stosie.
Koncepcja przeniesienia własności ("Move")
Kiedy przypisujemy zmienną przechowywaną na stercie do innej zmiennej, Rust przenosi (moves) własność wartości, unieważniając pierwszą zmienną:
let s1 = String::from("ciao");
let s2 = s1; // La proprieta del testo si sposta a s2. s1 NON e piu utilizzabile!
// println!("{}", s1); // ERRORE DI COMPILAZIONE! s1 e "borrow of moved value"
println!("{}", s2); // Valido!
To zachowanie pozwala uniknąć błędu podwójnego zwalniania pamięci (double free error) przy wyjściu z zakresu, ponieważ tylko s2 zwolni pamięć.
Koncepcja klonowania ("Clone" - głębokie kopiowanie)
Jeśli zachodzi jawna potrzeba skopiowania całej zawartości obiektu String (zarówno wskaźników na stosie, jak i rzeczywistych danych tekstowych na stercie), możemy użyć metody .clone(). Duplikuje to całkowicie dane na stercie, dzięki czemu zarówno oryginalna, jak i nowa zmienna pozostają ważne, kosztem wydajności wynikającym z nowej alokacji pamięci:
let s1 = String::from("ciao");
let s2 = s1.clone(); // Copia profonda. Entrambe le variabili rimangono valide!
println!("s1: {}, s2: {}", s1, s2);
Koncepcja kopiowania ("Copy")
W przypadku prostych typów przechowywanych w całości na stosie (takich jak liczby całkowite i32, wartości logiczne bool, znaki char), przypisanie wykonuje rzeczywistą automatyczną kopię powierzchowną, dzięki czemu obie zmienne pozostają ważne:
let x = 5;
let y = x; // Copia il valore 5 nello stack. Entrambe le variabili sono utilizzabili!
println!("x: {}, y: {}", x, y); // Valido!
Spróbuj sam
Zadeklaruj String s1 zawierający tekst 'hello'. Przypisz s1 do s2 (w celu przeniesienia własności). Na koniec wypisz s2 na ekranie za pomocą println!.
Pokaż wskazówkę
Użyj `let s2 = s1;` do przeniesienia własności do `s2` i wypisz ją za pomocą `println!('{}', s2);`.
Rozwiązanie dostępne po 3 próbach
Zadeklaruj zmienną całkowitą x o wartości 5. Przypisz x do y (wykonując kopiowanie). Na koniec wypisz zarówno x, jak i y w tej samej instrukcji println!.
Pokaż wskazówkę
Zapisz `let y = x;` a następnie wypisz obie wartości, np. `println!('{} {}', x, y);`.
Rozwiązanie dostępne po 3 próbach
Zadeklaruj zmienną String s1 o wartości "Rust". Utwórz głęboką kopię s1 w nowej zmiennej s2 za pomocą metody clone. Na koniec wypisz s1 oraz s2 oddzielone spacją za pomocą makra println!.
Pokaż wskazówkę
Użyj `let s2 = s1.clone();` do głębokiego skopiowania wartości `s1` do `s2`, a następnie wypisz je za pomocą `println!("{} {}", s1, s2);`.
Rozwiązanie dostępne po 3 próbach