The drop check¶
Level: 301 · deep dive
One line: A Vec<&String> may be dropped after the String it borrows is already gone, because Vec's destructor promises not to read its elements — a promise std makes with the unstable #[may_dangle] and your own impl Drop cannot, which is why the same shape with your type is E0597.
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
What it has to cover¶
- Drop order first: locals are dropped in reverse order of declaration — rustc's own note says "values in a scope are dropped in the opposite order they are defined". So in
let mut v = Vec::new(); let s = String::from("a"); v.push(&s);,sgoes first andvholds a dangling&swhile it is being dropped. Why does that compile? - The same shape with
struct Holder<'a>(&'a String)and animpl Drop for Holder<'_>—let h; let t = String::from("b"); h = Holder(&t);— isE0597: "tdropped here while still borrowed … borrow might be used here, whenhis dropped and runs theDropcode for typeHolder". Remove theimpl Dropand it compiles. What exactly did adding a destructor change? #[may_dangle]: std's source writesunsafe impl<#[may_dangle] T, …> DropforVec,VecDequeandBox, promising the destructor will dropTvalues without otherwise looking at them. On stable the attribute isE0658("may_danglehas unstable semantics and may be removed in the future", issue #34761); the Nomicon's drop check chapter ↗ shows it behind#![feature(dropck_eyepatch)]. Why is theimplunsafe?PhantomData<T>for ownership: a type that holds*const Tand dropsTs in its destructor must say it owns aT, or the drop check does not know to checkTat all. The claim table on Phantom types — which row does a raw-pointer collection need?- A trait object or a generic whose destructor does read the borrowed data: the case the rule exists to refuse, shown as a program that would print freed memory if it compiled.
- Adding a
Dropimpl to a public generic type that holds references: can that break a downstream crate that compiled yesterday? ManuallyDrop: wrapping theHolderabove inManuallyDrop::newmakes theE0597disappear, because nothing will run its destructor at the end of the scope. What has that moved onto you, and when is it the right answer?
The trap it exists for¶
A Drop impl added for logging to a struct that holds a reference. Code that has compiled for months now fails with E0597 wherever a holder is declared before the value it borrows, and the error names the struct's destructor rather than anything that looks wrong.
Where this sits¶
- The drop flag covers whether a location still holds a value at run time;
Drop, and what RAII buys covers when a destructor runs; Phantom types coversPhantomData's three spellings. This page covers only the borrow-check rule about what a destructor may see.
See also¶
- The drop flag — the run-time half of dropping
- Phantom types —
PhantomData<T>as the claim that a struct owns aT, which is what the drop check reads Drop, and what RAII buys — the destructor whose existence changes the rule- Lifetime annotations — the
'ainHolder<'a> unsafe traitandunsafe impl—unsafe impl Dropis one more promise signed at theimpl- What
unsafeturns off — the borrow checker stays on, and this is one of its rules
If you are coming from another language¶
- C++. Destructors run in reverse order of construction too, and a
std::vector<std::string_view>whose strings died first is destroyed without complaint — the compiler checks nothing, and a destructor that reads a dangling view is undefined behaviour at run time. Rust's drop check is that same situation turned into a compile-time question: will this destructor look? - Python. No counterpart, and the reason is instructive: a list holding a string keeps the string alive by reference count, so the referent cannot die first.
__del__never sees a freed object; Rust's borrowed-data model is what makes the question possible. - Go. A garbage collector plus finalizers with no ordering guarantee —
runtime.SetFinalizerruns at some point after the object becomes unreachable — so there is no destructor order to reason about and nothing likeE0597.
Po polsku¶
Sprawdzanie przy upuszczaniu (drop check) to reguła borrow checkera o tym, co destruktor może zobaczyć. Zmienne lokalne są niszczone w odwrotnej kolejności deklaracji, więc Vec<&String> bywa upuszczany już po zniknięciu Stringa — i to się kompiluje, bo destruktor Vec obiecuje (niestabilnym atrybutem #[may_dangle]), że elementów nie czyta. Twoja struktura z referencją i własnym impl Drop takiej obietnicy złożyć nie może, dlatego identyczny kod daje E0597. Pułapka: dopisanie Drop „tylko do logowania” potrafi zepsuć kod, który od miesięcy działał.
Szukaj po polsku: kolejność niszczenia zmiennych · destruktor w Ruscie · rust dropck may_dangle · rust E0597 when dropped runs the Drop code · nomicon drop check