RozdziałyPosiadanieReferencje i pożyczki

Referencje i pożyczki

Przekazywanie wartości przez referencję

  1. Referencja do wartości definiowana jest za pomocą operatora &, np.
    let s = String::from("sample");
    let s_ref = &s;
  2. Definicja parametrów w postaci referencji nazywa się pożyczaniem wartości. W takim przypadku funkcja nie posiada pożyczonej wartości i musi ją oddać po zakończeniu działania.
    fn main() {
        let s1 = String::from("sample");
        let s2 = process_text(&s1);  // lends the s1 value through a reference
        println!("{} -> {}", s1, s2) // both s1 and s2 are valid
    }
     
    fn process_text(s: &String) -> String { // borrow s value and return new string
        s.to_uppercase()
    }

Mutowalne referencje

  1. Wrócmy do funkcji, która chce przetworzyć podany tekst (tym razem będziemy chcieli skrócić dany tekst), ale spróbujmy zrobić to za pomocą pożyczenia wartości
    fn main() {
        let mut s1 = String::from("sample text");
        process_text(&s1, 4);
        println!("{}", s1);
    }
     
    fn process_text(s: &String, len : usize ) {
        s.truncate(len)
    }
  2. Uruchom program i zastanów się na błędami kompilacji
  3. Aby poprawić kod, nie tylko zmienna musi być mutowalna, ale również musimy jawnie wypożyczyć wartość w sposób, który umożliwi modyfikację wartości
    fn main() {
        let mut s1 = String::from("sample text");
        process_text(&mut s1, 4);
        println!("{}", s1);
    }
     
    fn process_text(s: &mut String, len : usize) {
        s.truncate(len)
    }

Praca z referencjami

Reguły dotyczące referencji

W dowolnym momencie programu, dla jednej wartości możemy mieć tylko jeden z poniższych przypadków:

  • jedną mutowalną referencję,
  • dowolną liczbę niemutowalnych referencji.

Ponadto wszystkie odwołania muszą być ważne, ie. muszą odnosić się do wartości, które nie są poza zakresem.

Uruchom kolejne fragmenty kodu i wyjaśnij, której reguły nie spełnia

let mut s = String::from("sample");
let s1 = &mut s;
let s2 = &mut s;
 
println!("{}, {}", s1, s2);
let mut s = String::from("sample");
let s1 = &mut s;
let s2 = &s;
 
println!("{}, {}", s1, s2);
let mut s = String::from("sample");
let s1 = &mut s;
let s2 = s;
 
println!("{}, {}", s1, s2);
fn main() {
    let s = generate_ref_to_string();
}
 
fn generate_ref_to_string() -> &String {
    &String::from("dangling text")
}

Ćwiczenie - Popraw kod

  1. Popraw poniższy kod tak, aby się skompilował, możesz tylko zmienić kolejność linii w funkcji main. Wyjaśnij dlaczego kod po zmianach jest poprawny.
    fn main() {
        let mut x = 100;
        let y = &mut x;
        let z = &mut x;
        *y += 100;
        *z += 1000;
        assert_eq!(x, 1200);
    }
  2. Popraw kod jedynie poprzez dodawanie lub usuwanie referencji
    fn main() {
        let data = "Rust is great!".to_string();
     
        get_char(data);
     
        string_uppercase(&data);
    }
     
    // Should not take ownership
    fn get_char(data: String) -> char {
        data.chars().last().unwrap()
    }
     
    // Should take ownership
    fn string_uppercase(mut data: &String) {
        data = &data.to_uppercase();
     
        println!("{}", data);
    }

Źródło: powyższe zadania pochodzą z oficjalnych ćwiczeń rustlings, dostępnych w repozytorium https://github.com/rust-lang/rustlings/. Znajdziesz tam dużo więcej podobnych zadań.