Lekcje modułu (2/2)
Referencje i Borrowing
W języku Rust ciągłe przekazywanie własności (ownership) zmiennej do funkcji i jej zwracanie z powrotem może być bardzo niewygodne. Aby rozwiązać ten problem, Rust wykorzystuje referencje (references).
Tworzenie referencji do wartości nazywane jest pożyczaniem (Borrowing).
Referencje niezmienne
Referencję deklaruje się poprzez umieszczenie symbolu & przed typem lub zmienną. Domyślnie referencje są niezmienne (immutabili): pozwalają na odczyt wartości, ale nie na jej modyfikację.
fn main() {
let s1 = String::from("hello");
// Passiamo un riferimento a s1, non l'ownership
let len = calculate_length(&s1);
// s1 è ancora utilizzabile qui!
println!("La lunghezza di '{}' è {}.", s1, len);
}
fn calculate_length(s: &String) -> usize { // s è un riferimento a una String
s.len()
} // Qui s esce dallo scope, ma poichè non possiede il valore, non succede nulla
Referencje modyfikowalne
Jeśli potrzebujesz zmodyfikować pożyczoną wartość, musisz użyć referencji modyfikowalnej za pomocą &mut. Oryginalna zmienna również musi być zadeklarowana jako modyfikowalna za pomocą mut:
fn main() {
let mut s = String::from("hello");
// Passiamo un riferimento mutabile
change(&mut s);
println!("{}", s); // Stampa "hello, world"
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Złote zasady pożyczania (Borrowing)
Aby zapobiec uszkodzeniom pamięci i wyścigom danych (data races) w czasie kompilacji, Rust narzuca dwie fundamentalne reguły:
- Możesz mieć dowolną liczbę niezmiennych referencji (
&T) do danej wartości w tym samym czasie. - ALBO możesz mieć dokładnie jedną modyfikowalną referencję (
&mut T) do wartości w danym momencie.
W żadnym wypadku nie wolno mieszać referencji niezmiennych i modyfikowalnych dla tej samej wartości w tym samym zakresie (scope):
let mut s = String::from("hello");
let r1 = &s; // Valido
let r2 = &s; // Valido
// let r3 = &mut s; // ERRORE DI COMPILAZIONE! Non puoi creare &mut s se s è già presa in prestito come immutabile
Zakres (Scope) referencji i Non-Lexical Lifetimes (NLL)
Dawniej zakres ważności referencji trwał obowiązkowo do końca bloku, w którym została utworzona. Dziś kompilator Rusta jest inteligentniejszy dzięki Non-Lexical Lifetimes (NLL): zakres referencji kończy się w ostatniej linii, w której jest ona używana, a niekoniecznie na końcu bloku.
Dzięki temu poniższy kod jest w pełni poprawny:
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} e {}", r1, r2); // Ultimo uso di r1 e r2. I riferimenti immutabili scadono qui!
let r3 = &mut s; // Valido! Nessun riferimento immutabile è attivo a questo punto
Spróbuj sam
Przekaż niezmienną referencję s1 do funkcji calculate_length przy użyciu symbolu &.
Pokaż wskazówkę
Zastąp `/* TODO \_/`zapisem`&s1`, aby przekazać niezmienną referencję do ciągu znaków.
Rozwiązanie dostępne po 3 próbach
Uczyń zmienną s modyfikowalną (let mut s) i przekaż modyfikowalną referencję (&mut s) do funkcji change, aby umożliwić jej modyfikację ciągu znaków.
Pokaż wskazówkę
Użyj `let mut s`zamiast`let s`i wywołaj`change(&mut s);`.
Rozwiązanie dostępne po 3 próbach
Zadeklaruj zmienną s zawierającą String::from("Rust"). Utwórz dwie niezależne niezmienne referencje r1 i r2 do s, a na koniec wypisz obie referencje oddzielone spacją za pomocą makra println!.
Pokaż wskazówkę
Przypisz `let r1 = &s;` oraz `let r2 = &s;` do pobrania dwóch niezmiennych referencji, a następnie wypisz je za pomocą `println!("{} {}", r1, r2);`.
Rozwiązanie dostępne po 3 próbach