Dowiedz się więcej o wypożyczeniu

Ukończone

W poprzednim module dowiedzieliśmy się, jak wartości mają właścicieli. Przenosimy własność wartości, przenosząc własność z jednej zmiennej na inną. Nie można przenieść własności dla typów implementujących cechę Copy , na przykład dla prostych wartości, takich jak liczby.

Możemy również jawnie skopiować wartości przy użyciu procesu klonowania . Wywołujemy metodę clone i pobieramy nowe wartości, które są kopiowane, co pozostawia oryginalne wartości niezawzorowane i wolne do użycia.

Czy nie byłoby miło, aby umożliwić funkcjom i innym zmiennym używanie pewnych danych bez pełnego posiadania?

Ten typ funkcjonalności jest dostępny przy użyciu odwołań. Odwołania umożliwiają nam "pożyczanie" wartości bez przejmowania własności.

let greeting = String::from("hello");
let greeting_reference = &greeting; // We borrow `greeting` but the string data is still owned by `greeting`
println!("Greeting: {}", greeting); // We can still use `greeting`

W poprzednim kodzie greeting został pożyczony przy użyciu symbolu odwołania (&). Zmienna greeting_reference była odwołaniem do ciągu typu (&String). Ponieważ tylko pożyczyliśmy greeting i nie przenieśliśmy własności, greeting nadal można go użyć po utworzeniu greeting_reference.

Odwołania w funkcjach

Ten przykład nie jest zbyt interesujący, ponieważ nie używamy greeting_reference niczego. Przyjrzyjmy się, jak używamy odwołań w funkcjach.

fn print_greeting(message: &String) {
  println!("Greeting: {}", message);
}

fn main() {
  let greeting = String::from("Hello");
  print_greeting(&greeting); // `print_greeting` takes a `&String` not an owned `String` so we borrow `greeting` with `&`
  print_greeting(&greeting); // Since `greeting` didn't move into `print_greeting` we can use it again
}

Pożyczki pozwalają nam korzystać z wartości bez przejęcia pełnej własności. Jednak, jak zobaczymy, zaciąganie wartości oznacza, że nie możemy zrobić wszystkiego, co możemy zrobić z w pełni własnością.

Mutowanie wartości pożyczonych

Co się stanie, jeśli spróbujemy zmutować wartość, jaką pożyczyliśmy?

fn change(message: &String) {
  message.push_str("!"); // We try to add a "!" to the end of our message
}

fn main() {
  let greeting = String::from("Hello");
  change(&greeting); 
}

Ten kod nie zostanie skompilowany. Zamiast tego występuje następujący błąd kompilatora:

error[E0596]: cannot borrow `*message` as mutable, as it is behind a `&` reference
 --> src/main.rs:2:3
  |
1 | fn change(message: &String) {
  |                    ------- help: consider changing this to be a mutable reference: `&mut String`
2 |   message.push_str("!"); // We try to add a "!" to the end of our message
  |   ^^^^^^^ `message` is a `&` reference, so the data it refers to cannot be borrowed as mutable

Jeśli przyjrzysz się bliżej błędowi kompilatora, zobaczysz wskazówkę dotyczącą zmiany naszego odwołania na modyfikowalny , zmieniając parametr typu z &String na &mut String. Musimy również zadeklarować naszą oryginalną wartość jako modyfikowaną:

fn main() {
    let mut greeting = String::from("hello");
    change(&mut greeting);
}

fn change(text: &mut String) {
    text.push_str(", world");
}

W przypadku & pożyczek, znanych jako "niezmienne pożyczki", możemy odczytać dane, ale nie możemy go zmienić. W przypadku &mut pożyczek, znanych jako "modyfikowalne pożyczki", możemy zarówno odczytywać, jak i zmieniać dane.

Pożyczanie i modyfikowalne odwołania

Teraz możemy przejść do prawdziwego rdzenia historii zarządzania pamięcią Rusta. Niezmienne i modyfikowalne odwołania różnią się w inny sposób, który ma radykalny wpływ na sposób tworzenia naszych programów Rust. Gdy pożyczymy wartość dowolnego typu T, obowiązują następujące reguły:

Kod musi implementować jedną z następujących definicji, ale nie obie w tym samym czasie:

  • Co najmniej jedno niezmienialne odwołania (&T)
  • Dokładnie jedno modyfikowalne odwołanie (&mut T)

Poniższy kod nie ma dozwolonych definicji, więc kompilacja kończy się niepowodzeniem:

fn main() {
    let mut value = String::from("hello");

    let ref1 = &mut value;
    let ref2 = &mut value;

    println!("{}, {}", ref1, ref2);
}
    error[E0499]: cannot borrow `value` as mutable more than once at a time
     --> src/main.rs:5:16
      |
    4 |     let ref1 = &mut value;
      |                ---------- first mutable borrow occurs here
    5 |     let ref2 = &mut value;
      |                ^^^^^^^^^^ second mutable borrow occurs here
    6 |
    7 |     println!("{}, {}", ref1, ref2);
      |                        ---- first borrow later used here

Możemy nawet spróbować mieszać niezmienne odwołania z odwołaniami modyfikowalnymi, ale kompilator nadal będzie narzekać:

fn main() {
    let mut value = String::from("hello");

    let ref1 = &value;
    let ref2 = &mut value;

    println!("{}, {}", ref1, ref2);
}
    error[E0502]: cannot borrow `value` as mutable because it is also borrowed as immutable
     --> src/main.rs:5:16
      |
    4 |     let ref1 = &value;
      |                ------ immutable borrow occurs here
    5 |     let ref2 = &mut value;
      |                ^^^^^^^^^^ mutable borrow occurs here
    6 |
    7 |     println!("{}, {}", ref1, ref2);
      |                        ---- immutable borrow later used here

To ograniczenie może wydawać się na początku trudne, ale ten aspekt zaciągania pożyczek uniemożliwia kod Rust z wielu problemów, w tym nigdy nie ma wyścigów danych.