It was just pointed out to me that there is another way to cause memory leaks in Rust that I was not aware of yet -- without direct use of reference counting or mem::forget or so:
use std::sync::mpsc::{Receiver, channel};
struct R(Receiver<Box<R>>);
fn main() {
let (s, r) = channel::<Box<R>>();
s.send(Box::new(R(r))).unwrap();
drop(s);
}
Basically, we can define a channel with a fancy recursive type such that we can send the other end of the channel over itself. That way we never drop r.
(mpsc internally uses Arc, but this example would work even with an SPSC channel -- the leak is unrelated to the reference counting.)
Rust doesn't guarantee absence of leaks, so there is no bug here, but it's still a cute new memory leak gadget. I guess we are lucky that Rust 1.0 had Rc so there was a simpler gadget and we knew to give up on the memory leak freedom guarantee.
It's interesting to me that both this and the classic Rc/Arc examples are constructs for "sharing;" in one case by direct shared ownership and the other case by passing ownership through a queue. I wonder what sort of deeper connection there might be between memory leak freedom and sharing and the specific weaknesses of Rust's type system to analyse these cases.
I believe this means that if there were Leak bounds, there would be no way to share non-Leak types across thread boundaries. Is this right?
Interestingly, the Linux kernel has a simple garbage collector (in net/unix/garbage.c) to handle the analogous case of file descriptors sent over UNIX sockets.
Sharing in general is fine, but sharing can easily lead to cyclic structures and cycles are a problem. In this case it's the internal buffer of that channel pointing to itself. With a synchronous channel (without a buffer) this could not happen.
Hm... you may be right. That's now how I thought of the protocol but I guess it is an equivalent view.