Search code examples
ruststack-overflow

Why does this turn into a stack overflow?


Simple test case that fails with stack overflow:

// trait to say FnMut has a clone_box method
pub trait ClonableFnMut<A>: FnMut(A) {
    fn clone_box(&self) -> Box<dyn ClonableFnMut<A> + Send + 'static>;
}

// overridden .clone() for Box<ClonableFnMut> that calls .clone_box on f
impl<A: 'static> Clone for Box<dyn ClonableFnMut<A> + Send + 'static> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

// .clone_box() on FnMut clones itself and wraps itself in a new Box
impl<A, F: FnMut(A) + Clone + Send + 'static> ClonableFnMut<A> for F {
    fn clone_box(&self) -> Box<dyn ClonableFnMut<A> + Send + 'static> {
        Box::new(self.clone())
    }
}

fn main() {
    let mut f: Box<dyn ClonableFnMut<u8> + Send + 'static> = Box::new(|_x|{});

    println!("{:?}", f(3));
    println!("{:?}", f.clone()(4));
}

Theoretically:

  1. Call .clone() on Box<ClonableFnMut>.
  2. Custom implementation calls .clone_box() on the inner FnMut.
  3. The inner FnMut can now call .clone() on itself, since it's marked Clone.
  4. .clone_box() returns this cloned FnMut (self) in a new Box

But it actually:

  1. calls .clone() manually on Box<ClonableFnMut>.
  2. calls .clone_box() on inner Box<FnMut>.
  3. calls self.clone() which appears to mean self = box.
  4. custom Box<FnMut> clone() is called again, starts at step 1.

What's the actual reason for step 4 happening?


Solution

  • The thing happening actually is slightly different:

    1. Someone calls clone on Box...
    2. ...which calls clone_box on the same Box...
    3. ...which calls clone on the same Box again, closing the loop.

    This happens because your implementation of Clone calls just self.clone_box(), but clone_box is a method of the ClonableFnMut trait. This trait is implemented, in particular, on Box<dyn ClonableFnMut<A> + Send + 'static> due to the blanket implementation, the requirements of which F: FnMut(A) + Clone + Send + 'static are satisfied by the Box itself.

    To avoid this, you need to force the Clone implementation to call the clone_box method for the contents of the Box, not for the Box itself. There are two obvious ways in slightly different styles:

    1. Replace self.clone_box() with self.deref().clone_box() and add the required import use std::ops::Deref; somewhere.

    2. Alternatively, replace self.clone_box() with (**self).clone_box(), which does not require an extra import, but looks slightly cryptic. Note that the argument of clone, &self, is basically a syntactic sugar for self: &Self, so the first * dereferences it from &Box<F> to Box<F> and the second one dereferences it once more to F. Calling clone_box then auto-references it to &F as it requires &self too.