Search code examples
rustpolymorphismtraitstrait-objects

Factorize methods taking &Box<T> and &T as argument


I have a method that I need to call with a trait parameter (let's call it Listener). The reason is that sometimes I have previously stored this trait parameter into a parent structure so it is inside a Box, and sometimes not.

So I have the two methods :

  • fref<T>(t: &T) where T: Listener
  • fbox(t: &Box<dyn Listener>)

and I would like both of them to call f(t: ??). For now I duplicated the code in fref and fbox which works but is not good. So I am looking for a signature of f that would make it callable from fref and fbox to factorize my code. I was hoping one of the traits implemented by Box would be equivalent to a & (or at least find a common ground somewhere).

I tried the following:

  • Writing f<T>(t: &T) where T: Listener but then I can't call from fbox (Listener is not implemented by Box<dyn Listener>).
  • Then changing the call from within fbox to f(&*t) to unbox my Box<Listener> but since t is not Sized I can't.

  • Writing f<T>(t: &T) where T: std::borrow::Borrow<Listener> but then I can't call from fref (Borrow is not implemented by Listener)

  • Same with AsRef<Listener>
  • Last attempt with Deref playground:
trait Listener {}
struct Mouse {}
impl Listener for Mouse {}

fn fbox(t: &Box<Listener>) {
    f(t);
}

fn fref<T>(t: &T)
where
    T: Listener,
{
    f(t);
}

fn f<T>(_t: &T)
where
    T: std::ops::Deref<Target = Listener>,
{

}

fn create_listener() -> impl Listener {
    Mouse {}
}

fn main() {
    let mouse = create_listener();
    let box_mouse: Box<Listener> = Box::new(Mouse {});

    fref(&mouse);
    fbox(&box_mouse);
}

Solution

  • Listener is a trait, so Box<Listener> is really a trait object, Box<dyn Listener> - it is unfortunate that the dyn keyword is currently optional. Both Box<dyn Listener> and &Mouse implement Deref, with an associated Target type that implements Listener. In the case of &Mouse the deref Target is Mouse, but in the case of Box<dyn Listener> it is an unknown object, dyn Listener of unknown size.

    To capture all of that information, you can write f like this:

    fn f<T, L>(_listener: &T)
    where
        T: Deref<Target = L>,
        L: Listener + ?Sized
    {
    }
    

    And call it from each function like this:

    fn fbox(listener: &Box<dyn Listener>) {
        f(listener);
    }
    
    fn fref<L>(listener: &L)
    where
        L: Listener
    {
        f(&listener);
    }
    

    Another, perhaps simpler way of looking at this is to forgo the Deref constraint and just use normal references. Use Box::as_ref to turn a Box in to a reference in order to call it. The ?Sized un-constraint is still needed for the trait object case, and still works since the value is always behind a pointer:

    fn fbox(listener: &Box<dyn Listener>) {
        f(listener.as_ref());
    }
    
    fn fref<L>(listener: &L) where L: Listener {
        f(listener);
    }
    
    fn f<L>(_listener: &L)
    where
        L: Listener + ?Sized
    {
    }