Putting stuff in a HashMap that you'll never ask for again is a kind of leak. Allocating stuff on the stack and then looping forever is a kind of leak. The case where you put it on the heap and forget to ever free it is a specific kind of leak that people seem particularly terrified of for whatever reason.
I too am particularly terrified of this kind of leak. The reason is that it is the only kind of leak mentioned that is not clearly a programmer mistake.
C++ has a whole class of "mistakes" that one cannot be aware of simply by reading the language specification. Common ones such as writing a constructor and destructor but not a copy-constructor are now compiler warnings, but many will pass under the radar. As a result, to write correct code one needs to be aware of multiple conventions and rules and patterns that are external to the language. When reviewing code one needs to do more than observe that it compiles and accomplishes its stated task, there are probably several organizational standards that need to be satisfied as well to be safe.
This is precisely the kind of thing that Rust was supposed to solve. The goal was to have a language that didn't require an "Effective <xyz>" book to be memorized to do code review. A language where any rule like "You should always free resources in advance in case the destructor doesn't run" is captured statically by a compiler error.
In Rust, you cannot put something on the heap and "forget" to ever free it. Rust doesn't expose any mechanism in safe code to heap-allocate something that won't automatically free it for you when that thing goes out of scope (the primary way to do this is with the Box type).
As evidenced by this whole kerfuffle, you can in fact leak heap-allocated values by constructing an Rc cycle (or taking advantage of various implementation bugs in other things), or by using mem::forget() (which I believe is still marked as unsafe but seems likely to change). But none of these approaches could be characterized as "forget[ting] to ever free it", because they're not a consequence of "oops I forgot to do that".
> A language where any rule like "You should always free resources in advance in case the destructor doesn't run" is captured statically by a compiler error.
This only shows up if you're specifically opting into unsafe code by using the "unsafe" keyword. Rust doesn't (yet) try to ensure safety of unsafe code, because it's unsafe. As long as you don't step into unsafe code, you can ignore this entire article.
Which unsafe code is that? In the article, I don't see any use of "unsafe" in safe_forget[1] or either main[2] in the first part of the article.
[1] Which creates a reference-counted cycle containing the thing-to-be-forgotten, ensuring the finalizer is never called.
[2] The first main simply demonstrates that the destructor is never called, which is ok in itself but causes problems when you're doing smart things in the finalizers, as in the second main, where the code uses the destructors to ensure the threads are terminated and the programmer is assuming the borrow checker is going to warn them if the threads aren't terminated.
Right. That block is unsafe because it's doing unsafe things. (ptr::read and alloc::heap::deallocate)
But the use of Rc is kind of a red herring; if mem::forget is marked safe, then you don't need safe_forget because you can forget things (fail to execute their destructors, specifically) safely anyway.
The problem is that destructors aren't guaranteed; the bugs in the thready thing (and potentially unbounded other things) are symptoms. Drop needs big, red warning signs.
The power of a notation, and a type system, is in what it lets you not think about. The fact that destructors may not be called is, unfortunately, something you have to think about.
Well, it's worth noting that this error happened because unsafe library code relied on a property that wasn't actually true. The language itself is working just fine, Rust cannot and will not prevent all bugs.
Scrapping the guarantee that destructors will be called is tantamount to scrapping RAII. thread::scoped made the assumption that destructors were called before a borrowed value was returned, and it led to a memory-safety error. If you don't use unsafe, you won't get memory-safety errors, but you can get plenty of other nastiness.
The guarantee that destructors are called when values leave scope is fantastic. It means that one can leverage memory-safety into arbitrary-resource-acquisition-safety. It violates the principle of least astonishment in a brutal way if Rust's borrow checker can statically guarantee that a value no longer exists, but can't know whether or not its destructor has been called.
I too am particularly terrified of this kind of leak. The reason is that it is the only kind of leak mentioned that is not clearly a programmer mistake.
C++ has a whole class of "mistakes" that one cannot be aware of simply by reading the language specification. Common ones such as writing a constructor and destructor but not a copy-constructor are now compiler warnings, but many will pass under the radar. As a result, to write correct code one needs to be aware of multiple conventions and rules and patterns that are external to the language. When reviewing code one needs to do more than observe that it compiles and accomplishes its stated task, there are probably several organizational standards that need to be satisfied as well to be safe.
This is precisely the kind of thing that Rust was supposed to solve. The goal was to have a language that didn't require an "Effective <xyz>" book to be memorized to do code review. A language where any rule like "You should always free resources in advance in case the destructor doesn't run" is captured statically by a compiler error.
So yes, I am particularly put off by this error.