Co ma Rust zamiast garbage collectora?

95

Rozumiem, że Rust nie ma odśmiecacza pamięci i zastanawiam się, jak zwalnia się pamięć, gdy powiązanie wykracza poza zakres.

W tym przykładzie rozumiem, że Rust odzyskuje pamięć przydzieloną do „a”, gdy wychodzi ona poza zakres.

{
    let a = 4
}

Problem, który mam z tym, polega po pierwsze na tym, jak to się dzieje, a po drugie, czy nie jest to rodzaj czyszczenia pamięci? Czym różni się od „typowego” zbierania śmieci?

rix
źródło
12
„Deterministyczne okresy życia obiektów”. Podobnie jak C ++.
user2864740
@ user2864740 Ten przewodnik jest nieaktualny. Współczesnym zamiennikiem byłby prawdopodobnie doc.rust-lang.org/book/references-and-borrowing.html .
Veedrac,

Odpowiedzi:

74

Wyrzucanie elementów bezużytecznych jest zwykle używane okresowo lub na żądanie, na przykład gdy sterta jest bliska zapełnienia lub przekracza pewien próg. Następnie szuka nieużywanych zmiennych i zwalnia ich pamięć, w zależności od algorytmu .

Rust wiedziałby, kiedy zmienna znajdzie się poza zakresem lub jej żywotność kończy się w czasie kompilacji i w ten sposób wstawia odpowiednie instrukcje LLVM / assemblera, aby zwolnić pamięć.

Rust pozwala także na zbieranie śmieci, na przykład atomowe liczenie odwołań .

Ayonix
źródło
Przydzielając pamięć podczas wprowadzania zmiennych i zwalniając pamięć, gdy pamięć nie jest już potrzebna? Naprawdę nie wiem, co chcesz przez to powiedzieć. Może mamy wtedy różne opinie na temat tego, czym jest GC.
Ayonix,
1
Jego pytanie brzmi, czym podejście Rusta różni się od typowego GC. Więc wyjaśniłem, czym jest GC i jak Rust to robi bez GC.
Ayonix,
1
doc.rust-lang.org/book/the-stack-and-the-heap.html wyjaśnia to całkiem dobrze. Tak, wiele rzeczy jest na stosie, ale nie mówiąc już o tym, nie jest to wystarczający wskaźnik (patrz Ramka).
Pominąłem to
1
@Amomum Właściwie Rust nie ma żadnej namaszczonej new()funkcji, takiej jak C, są to po prostu funkcje statyczne, aw szczególności coś takiego, jak let x = MyStruct::new()tworzy swój obiekt na stosie. Rzeczywistym wskaźnikiem sterty alokacji jest Box::new()(lub dowolną ze struktur, które zależą Box).
Mario Carneiro,
1
Jakie inne języki obsługują zarządzanie pamięcią w podobny sposób jak Rust?
still_dreaming_1
43

Podstawowa idea zarządzania zasobami (w tym pamięcią) w programie, bez względu na strategię, polega na tym, że zasoby powiązane z nieosiągalnymi „obiektami” mogą zostać odzyskane. Oprócz pamięci, te zasoby mogą być blokadami mutex, uchwytami plików, gniazdami, połączeniami z bazą danych ...

Języki z modułem odśmiecania pamięci okresowo skanują pamięć (w ten czy inny sposób) w celu znalezienia nieużywanych obiektów, zwalniania zasobów z nimi powiązanych i wreszcie zwalniania pamięci używanej przez te obiekty.

Rust nie ma GC, jak sobie z tym radzi?

Rust ma własność. Korzystając z systemu typu afinicznego , śledzi, która zmienna wciąż trzyma obiekt, a gdy taka zmienna wychodzi poza zakres, wywołuje jej destruktor. Możesz łatwo zobaczyć działający system typów afinicznych:

fn main() {
    let s: String = "Hello, World!".into();
    let t = s;
    println!("{}", s);
}

Plony:

<anon>:4:24: 4:25 error: use of moved value: `s` [E0382]
<anon>:4         println!("{}", s);

<anon>:3:13: 3:14 note: `s` moved here because it has type `collections::string::String`, which is moved by default
<anon>:3         let t = s;
                     ^

co doskonale ilustruje, że w dowolnym momencie, na poziomie języka, własność jest śledzona.

Ta własność działa rekurencyjnie: jeśli masz a Vec<String>(tj. Dynamiczną tablicę ciągów), to każda Stringz Vecnich jest własnością zmiennej lub innego obiektu, itd ... tak więc, gdy zmienna wykracza poza zakres, rekurencyjnie zwalnia wszystkie posiadane zasoby, nawet pośrednio. W przypadku Vec<String>tego oznacza:

  1. Zwolnienie bufora pamięci związanego z każdym String
  2. Zwolnienie bufora pamięci skojarzonego z samym Vecsobą

W ten sposób, dzięki śledzeniu własności, czas życia WSZYSTKICH obiektów programu jest ściśle powiązany z jedną (lub kilkoma) zmiennymi funkcyjnymi, które ostatecznie wyjdą poza zakres (kiedy kończy się blok, do którego należą).

Uwaga: jest to nieco optymistyczne, używając liczenia referencji ( Rclub Arc) można tworzyć cykle odwołań i w ten sposób powodować wycieki pamięci, w którym to przypadku zasoby powiązane z cyklem mogą nigdy nie zostać zwolnione.

Matthieu M.
źródło
2
„Języki z programem Garbage Collector okresowo skanują pamięć (w ten czy inny sposób)”. Wielu to robi, ale generalnie nie jest to prawdą. Urządzenia odśmiecające w czasie rzeczywistym skanują raczej przyrostowo niż okresowo. Języki referencyjne, takie jak Mathematica, w ogóle nie skanują.
JD
@JonHarrop: Zliczanie odwołań nie jest dla mnie kompletnym mechanizmem zbierania elementów bezużytecznych, ponieważ należy je uzupełniać, aby uniknąć wycieków cykli. Jeśli chodzi o różnicę przyrostową / okresową, może to moja słaba znajomość języka angielskiego, ale nie widzę, jak okresowość nie obejmuje przypadku przyrostowego ... Myślę, że bit „(w taki czy inny sposób)” odpowiednio oddaje tak wiele różnych istnieją podejścia. W każdym razie, jeśli masz lepszy sposób na zwięzłe opisanie Garbage Collection, prosimy o sugestie. Nie mam jednak zamiaru wypowiadać się w pełnym wyjaśnieniu: nie mam do tego kwalifikacji.
Matthieu M.,
1
„Zliczanie referencji nie jest dla mnie kompletnym mechanizmem zbierania śmieci, ponieważ musi być uzupełniany, aby uniknąć przecieków”. RC jest tradycyjnie traktowane jako forma GC. Na przykład w Mathematica i Erlang cykle nie mogą być tworzone zgodnie z projektem, więc RC nie przecieka. Aby zapoznać się z perspektywą wyższego poziomu, zobacz „Ujednolicona teoria zbierania śmieci” cs.virginia.edu/~cs415/reading/bacon-garbage.pdf
JD,
@JonHarrop: Prawda, jeśli żaden cykl nie jest możliwy, RC nie może przeciekać.
Matthieu M.,
2
„Nie widzę, jak okresowość nie obejmuje przypadku przyrostowego”. Algorytmy Stop the world byłyby traktowane jako okresowe, podczas gdy trójkolorowe oznaczanie jest na przykład traktowane jako przyrostowe. W tym kontekście są przeciwieństwami.
JD,
6

W języku, w którym trzeba ręcznie zarządzać pamięcią, rozróżnienie między stosem a stertą staje się krytyczne. Za każdym razem, gdy wywołujesz funkcję, na stosie przydzielana jest wystarczająca ilość miejsca na wszystkie zmienne zawarte w zakresie tej funkcji. Kiedy funkcja zwraca, ramka stosu skojarzona z tą funkcją jest „zdejmowana” ze stosu, a pamięć jest zwalniana do wykorzystania w przyszłości.

Z praktycznego punktu widzenia to nieumyślne czyszczenie pamięci jest używane jako środek do automatycznego przechowywania pamięci, które zostanie wyczyszczone na końcu zakresu funkcji.

Więcej informacji można znaleźć tutaj: https://doc.rust-lang.org/book/the-stack-and-the-heap.html

szwajcarski
źródło
3
Chociaż używanie stosu jest przydatne, deterministyczne okresy istnienia obiektów nadal mogą być obsługiwane, jeśli wszystkie wartości zostały „utworzone na stercie”. Zatem jest to szczegół implementacji; niekoniecznie strategia językowa.
user2864740
2
Ciągle używasz tego słowa. Nie sądzę, że oznacza to, co myślisz, że to znaczy.
Szwajcaria
Oznacza to, co chcę wyrazić ; będąc przeciwieństwem niedeterministycznych wcieleń. Złóż ofertę na lepszą frazę.
user2864740
Dzięki za odpowiedź, przyznałem punkty pierwszemu, tylko dlatego, że został przesłany jako pierwszy. Informacje są równie przydatne i aktualne.
rix
@ user2864740 Deterministyczne okresy istnienia obiektów odnoszą się do możliwości dokładnego określenia, kiedy pamięć obiektu zostanie wyczyszczona po wywołaniu jego destruktora. Nie ma to nic wspólnego z tym, jak nazywa się ten destruktor. Wciąż powracasz do tego samego terminu, mimo że nie ma on bezpośredniego znaczenia dla pytania.
Szwajcaria