programowanieRust Backend Developer

Wyjaśnij, jak działa borrowing i aliasing w Rust. Jakie są ograniczenia dotyczące jednoczesnego używania mutowalnych i niemutowalnych referencji, jak jest to kontrolowane przez kompilator i co może pójść nie tak?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Borrowing (pożyczanie) w Rust to mechanizm tymczasowego "pożyczania" zmiennej za pomocą referencji. Rust rozróżnia niemutowalne pożyczanie (&T) i mutowalne (&mut T). Jednocześnie może istnieć dowolna liczba niemutowalnych referencji, ale tylko jedna mutowalna. Nie można jednocześnie mieć mutowalnej i niemutowalnej referencji do tego samego obiektu.

Ta zasada gwarantuje brak warunków wyścigu (data race) na etapie kompilacji i sprawia, że Rust jest bezpieczny dla programowania współbieżnego.

Przykład:

let mut value = 5; let r1 = &value; let r2 = &value; // let r3 = &mut value; // BŁĄD: nie można tworzyć &mut, gdy istnieje & println!("{} {}", r1, r2); // r3 jest zabroniony do końca obszaru widoczności r1/r2

Pytanie z pułapką

Pytanie: Czy można w jednym obszarze widoczności stworzyć jedną referencję &mut i dowolną liczbę referencji & do tego samego obiektu?

Typowa błędna odpowiedź: Tak, ale tylko jeśli nie nakładają się na siebie w zakresie życia.

Poprawna odpowiedź: Nie może jednocześnie istnieć mutowalna i niemutowalna referencja do tego samego obiektu (nawet jeśli ich użycia nie nakładają się statycznie w kodzie), dopóki jedna z nich żyje — inne są zabronione. Bezpieczeństwo sprawdza borrow checker.

Przykład:

let mut x = 10; let y = &x; let z = &mut x; // Błąd, y wciąż jest w obszarze widoczności println!("{}", y); // y potrzebne później

Przykłady rzeczywistych błędów z powodu braku wiedzy o niuansach tematu


Historia

W dużym projekcie z równoległym przetwarzaniem danych programista postanowił używać niemutowalnych referencji do wektora, a następnie próbował uzyskać mutowalną do sortowania. Kod działał w teście, ale przestał się kompilować po refaktoryzacji, ponieważ wydłużył się czas życia niemutowalnych referencji.


Historia

W wewnętrznej usłudze zmieniano strukturę przez &mut, zachowując przy tym referencje do pól do późniejszego przesłania do innego wątku. Powstał wyścig danych i awaria z powodu nieprzestrzegania zasad borrow — Rust chroni przed tym tylko na etapie kompilacji, ale błędy wystąpiły w blokach unsafe, w których gwarancje zostały zniesione.


Historia

Niepoprawna dokumentacja API: biblioteka przyjmowała jednocześnie & i &mut do różnych pól struktury, ale z powodu aliasingu naruszano inwariantność, co prowadziło do trudnych do uchwycenia błędów w usługach integrujących tę bibliotekę.