Lekcje modułu (2/2)
Inteligentne wskaźniki (Smart Pointers): Box i Rc
Inteligentny wskaźnik (smart pointer) to struktura danych, która zachowuje się jak wskaźnik, ale posiada dodatkowe metadane i funkcjonalności (takie jak zliczanie referencji lub automatyczne zarządzanie pamięcią).
W języku Rust zwykłe referencje (takie jak &) jedynie pożyczają dane. Z kolei inteligentne wskaźniki często stają się właścicielami danych, na które wskazują. Typowymi przykładami są String oraz Vec<T>, ale biblioteka standardowa dostarcza inne, bardziej wyspecjalizowane wskaźniki.
Box<T> do alokacji pamięci na stercie (Heap)
Najprostszym inteligentnym wskaźnikiem jest Box<T>, który przechowuje dane na stercie (heap) zamiast na stosie. Na stosie pozostaje jedynie wskaźnik do danych zaalokowanych na stercie.
Jest używany głównie w następujących przypadkach:
- Kiedy mamy typ, którego rozmiar nie może być znany w momencie kompilacji (jak typ rekurencyjny), a chcemy użyć wartości tego typu w kontekście wymagającym dokładnego rozmiaru.
- Kiedy chcemy przenieść duże ilości danych bez ich kopiowania.
Podstawowy przykład:
fn main() {
let b = Box::new(5); // alloca il valore 5 nello heap
println!("b = {}", *b); // de-referenzia con * per leggere il valore
}
Typy rekurencyjne i lista Cons (Cons List)
Typ rekurencyjny to typ, który może zawierać wartość samego siebie w swoim wnętrzu. Rust musi znać dokładny rozmiar typu w czasie kompilacji. Ponieważ głębokość typu rekurencyjnego mogłaby być nieskończona, kompilator wygeneruje błąd, chyba że użyjemy Box (który ma stały rozmiar, ponieważ jest wskaźnikiem):
enum List {
Cons(i32, Box<List>),
Nil,
}
Rc<T>: Zliczanie referencji (Reference Counting)
Istnieją przypadki, w których pojedyncza wartość może mieć wielu właścicieli. Inteligentny wskaźnik Rc<T> (Reference Counted) śledzi liczbę referencji do wartości, aby ustalić, czy wartość jest nadal używana. Jeśli licznik spadnie do zera, zasób jest bezpiecznie usuwany z pamięci.
[!NOTE]
Rc<T>może być używany wyłącznie w scenariuszach jednowątkowych (single-thread). W scenariuszach wielowątkowych (współbieżnych) należy stosowaćArc<T>(Atomic Reference Counted).
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a); // incrementa il contatore a 2
println!("Riferimenti: {}", Rc::strong_count(&a));
}
Spróbuj sam
Esercizio 1: Alokowanie za pomocą Box
Zaalokuj wartość całkowitą równą 42 na stercie (heap) za pomocą Box::new i przypisz ją do zmiennej o nazwie val. Następnie wypisz na ekranie wartość zapisaną w Box, jawnie ją dereferencjonując za pomocą operatora *.
Pokaż wskazówkę
Użyj `let val = Box::new(42);` do zapisania wartości na stercie i dereferencjonuj ją za pomocą `*val` wewnątrz `println!`.
Rozwiązanie dostępne po 3 próbach
Esercizio 2: Rekurencyjna struktura danych (Cons List)
Zdefiniuj rekurencyjny enum o nazwie List zawierający dwa warianty: Cons, który zawiera krotkę (i32, Box<List>), oraz Nil reprezentujący koniec listy. W funkcji main utwórz instancję listy wiązanej zawierającej element 1, po którym następuje Nil.
Pokaż wskazówkę
Enum deklaruje się jako `enum List { Cons(i32, Box<List>), Nil }`. Utwórz listę za pomocą `List::Cons(1, Box::new(List::Nil))`.
Rozwiązanie dostępne po 3 próbach
Esercizio 3: Dzielenie własności z Rc
Użyj std::rc::Rc do utworzenia współdzielonej liczby całkowitej zawierającej wartość 100 w zmiennej a. Sklonuj referencję w zmiennej b przy użyciu Rc::clone. Na koniec wypisz na ekranie licznik aktywnych referencji przy użyciu funkcji Rc::strong_count.
Pokaż wskazówkę
Utwórz wartość za pomocą `Rc::new(100)`. Sklonuj ją za pomocą `Rc::clone(&a)`i wyświetl liczbę właścicieli za pomocą`Rc::strong_count(&a)`.
Rozwiązanie dostępne po 3 próbach