Historia pytania:
Rust od początku był zaprojektowany jako język, którego priorytetem jest bezpieczeństwo pamięci. Jednak w niektórych zadaniach — na przykład przy pracy z FFI lub niskopoziomowymi alokatorami — trzeba używać raw pointers i zarządzać dynamiczną pamięcią ręcznie. Takie zadania występują zarówno w programowaniu systemowym, jak i przy optymalizacji wydajności. Dlatego ważne jest, aby wiedzieć, jak Rust zapobiega wyciekom pamięci, dangling pointers i use-after-free.
Problem:
Raw pointers (*const T, *mut T) nie są zintegrowane z systemem własności i kontroli odniesień Rust: mogą wskazywać na niewłaściwą pamięć, być niewłaściwie zwolnione lub w ogóle nie być zwolnione. Błąd w operacjach na nich może prowadzić do UB (undefined behavior), awarii, luk w bezpieczeństwie lub wycieków pamięci.
Rozwiązanie:
Zamiast raw pointers zaleca się używanie bezpiecznych typów — Box, Rc, Arc, a dla tymczasowych odniesień — borrow-sygnatur. Jeśli jednak nie da się obejść bez raw pointers (na przykład do pracy z C API), całą pracę owija się w bloki unsafe, starannie organizuje Drop i jeśli to możliwe, używa się crates typu NonNull. Jeszcze jedną techniką są opakowania RAII i minimalizacja cyklu życia wskaźnika.
Przykład kodu:
fn allocate_in_heap() -> Box<i32> { Box::new(100) } // pamięć zostanie zwolniona automatycznie // z raw pointer unsafe fn leak_memory() { let ptr = libc::malloc(4) as *mut i32; if !ptr.is_null() { *ptr = 42; // libc::free(ptr); // jeśli zapomnimy zwolnić — wyciek! } }
Kluczowe cechy:
Czy Box gwarantuje automatyczne oczyszczanie wszystkich zagnieżdżonych wartości podczas usuwania Box?
Tak, przy usuwaniu Box<T> destruktor najpierw wywołuje oczyszczanie samego opakowania, a następnie rekurencyjnie — wszystkich danych zagnieżdżonych wewnątrz (włącznie z elementami Vec lub innymi Box w strukturze T).
Czy można bezpiecznie przekazać raw pointer struktury przez kilka funkcji, nie ryzykując uzyskania use-after-free?
Nie, raw pointer nie niesie informacji o czasie życia obiektu. Kompilator nie może sprawdzić bezpieczeństwa, dlatego pełna odpowiedzialność spoczywa na programiście: jeśli obiekt zostanie zwolniony, raw pointer będzie wskazywał w pustkę.
Czy jeśli ręcznie użyję free lub drop_in_place, może Rust wywołać Drop dwa razy dla tego samego adresu?
Tak, jeśli po ręcznym zwolnieniu pozostawi się inny Box/wskaźnik wskazujący na ten sam blok, to przy zniszczeniu drugiego egzemplarza Drop zostanie wywołane ponownie, co wywoła UB. Nigdy nie należy ręcznie zwalniać tego, czym zarządza Box, Vec itd.
Programista przyjął raw pointer z zewnętrznej biblioteki C, nie zwolnił po użyciu lub perfnodo-dealloc pomylił się z czasem życia.
Zalety:
Wady:
Używana jest opakowanie RAII z Drop, wskaźnik inkapsuluje się przez Box lub NonNull, wszystko bezpiecznie niszczy się na końcu scope.
Zalety:
Wady: