Dowiedz się więcej o wypożyczeniu
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.