programowanieProgramista Rust

Co to są lifetimes w Rust i po co są potrzebne przy pracy z referencjami?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W Rust lifetimes określają zakres widoczności referencji, aby kompilator mógł upewnić się, że wskaźniki nie są pozostawione w powietrzu (nie ma wiszących referencji). Pozwala to zapewnić bezpieczeństwo pamięci w czasie kompilacji bez potrzeby używania zbieracza śmieci.

Gdy pracujesz z referencjami, Rust wymaga wyraźnego określenia ich czasu życia, gdy kompilator nie może tego wydedukować samodzielnie. Zwykle robi się to za pomocą składni 'a:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

Tutaj oba parametry i wartość zwracana mają ten sam lifetime, co gwarantuje: zwracana referencja nie będzie żyła dłużej niż jakikolwiek z argumentów.

Lifetimes nie zmieniają czasu życia danych, a jedynie opisują go kompilatorowi.

Pytanie z podstępem

Czy można zwrócić referencję na lokalną zmienną wewnątrz funkcji?

Nie, nie można: ponieważ taka zmienna zostanie zniszczona po wyjściu z funkcji. Przykład:

fn foo() -> &String { // błąd kompilacji! let s = String::from("hello"); &s } // referencja na s staje się nieważna

Kompilator nie pozwoli na skompilowanie takiego kodu: ochroni cię przed używaniem referencji na zniszczone dane.

Przykłady rzeczywistych błędów z powodu nieznajomości niuansów tematu


Historia

W zespole pojawiło się wiele wycieków pamięci, gdy funkcje przypadkowo zwracały referencje na lokalne bufory. To nie zadziałało, a uratował nas tylko kompilator, który zaczął narzekać na lifetimes. Z powodu częstych wystąpień takich błędów przyjęto zasadę wyraźnego określania lifetime, jeśli funkcja pracuje złożonymi strukturami z zagnieżdżonymi referencjami.


Historia

W projekcie napisano kod generics do cache'owania danych. Przy niewłaściwym zaprojektowaniu parametry lifetimes generics pojawiały się błędy typu "cannot infer lifetime" i stało się niemożliwe wydedukowanie czasu życia danych przechowywanych w cache. Prowadziło to do dostosowywania adnotacji lifetime metodą prób i błędów, aż podjęto decyzję o podzieleniu danych cachowanych i niecachowanych na różne struktury.


Historia

Jeden z kolegów próbował zaimplementować pulę połączeń, używając referencji do obiektów połączeń, ale nie uwzględniał, że lifetimes połączeń nie pokrywają się z czasem życia puli. W rezultacie pojawiły się wiszące referencje po zwolnieniu połączeń, co zauważono dopiero na etapie kompleksowego testowania. Po tym projekcje przeszło na bezpieczne opakowania (Arc<Mutex<T>>).