Trait lrs::marker::Leak

Objects that can be leaked without causing memory unsafety.

Syntax

unsafe trait Leak

Remarks

In normal, safe code, the compiler inserts calls to destructors at the end of object's lifetimes, e.g,

{
    let x = X;
}

inserts a call to the X destructor at the end of the block. However, the compiler cannot do this if it doesn't know the lifetime of an object. This happens when you use raw pointers which opt out of lifetimes. In those cases it is the job of the programmer to insert destructor calls. For example, a vector looks roughly like this:

struct Vec<T> {
    ptr: *mut T,
    len: usize,
    cap: usize,
}

Since the Ts contained in the vector are behind a raw pointer, the compiler does not destroy them automatically at the end of their lifetime. The author of the Vec structure has to manually call the destructor for all of its T objects in the destructor of Vec:

fn drop(&mut self) {
    for i in 0..self.len {
        unsafe {
            // Bring the contained object back from behind the pointer so that it's
            // once again managed by the compiler which will call the destructor of
            // `_t` at the end of the `unsafe` block.
            let _t = ptr::read(self.ptr.add(i));
        }
    }
    // Deallocate `self.ptr` here
    // ...
}

In the case of vectors this is very easy: Since the lifetime of the vector is always shorter than the lifetime of the contained objects, we can rely on the drop code above running before the end of the lifetimes of the T.

There are, however, some data structures for which this is not easy. For example, the destructor of Rc looks like this:

fn drop(&mut self) {
    self.number_of_references -= 1;
    if self.number_of_references == 0 {
        // Call the destructor of the contained object
    }
}

As you can see, we don't always call the destructor of the contained object at the end of the lifetime of the Rc. At first it seems that this is no problem since, in order to have number_of_references > 1 you have to have cloned the Rc and all of the clones are bound by the lifetime of T. Once the last cloned Rc is dropped, it should call the destructor of the T.

But this doesn't account for the possibility of cycles. If we put a clone of the Rc into the Rc itself, then the destructor that destroys the last Rc clone will never run and thus the T will never be destroyed. Here is an example that generates such a cycle:

struct T;
 
impl Drop for T {
    fn drop(&mut self) {
        println!("dropped");
    }
}
 
struct X<T> {
    _t: T,
    rc: Option<Rc<RefCell<X<T>>>>,
}
 
fn main() {
    let rc = Rc::new(RefCell::new(X { _t: T, rc: None }));
    rc.borrow_mut().rc = Some(rc.clone());
}

You will notice that the dropped message will never be printed even though the end of the lifetime of T is reached at the end of the main block.

Most of the time this is not a problem, even if destructors don't run, this cannot cause memory unsafety. However, the safety of some structures depends on the guarantee that destructors run at the end of object's lifetimes. For example, the JoinGuard returned by thread::scoped must have its destructor run at the end of its lifetime or the behavior is undefined.

For this reason we introduce the Leak trait which marks objects that don't need to have their destructor run at the end of their lifetime. By default, every object is Leak. If your object contains an object that is !Leak, then it's automatically !Leak itself but you can opt into Leak by implementing Leak explicitly. If you want to explicitly opt out of Leak, then you have to implement !Leak for your object. For example, JoinGuard explicitly implements !Leak.

If you create a new container such as Vec or Rc which owns objects behind raw pointers and if you cannot guarantee that the object's destructors will be run at the end of their lifetimes, then you have to add the Leak bound to your trait bounds. For example, the Rc type can be defined as follows:

struct Rc<T: Leak> {
    data: *mut Inner<T>,
}

where Inner<T> is an allocated object that contains the T and the reference count.

The simple criterion is as follows:

If the destructor of your object calls the destructor of the contained object only if a certain condition is met, add the Leak bound.