2

I'm trying to make a trait that can either retrieve (and return a reference to) a trait object of another trait, or create one (and return a boxed version of it), leaving the choice to the implementor (which means I need to restrict the returned object's lifetime to that of the producer). However, I'm running into errors:

use std::borrow::Borrow;
use std::collections::HashMap;

trait A { 
    fn foobar(&self) {
        println!("!"); 
    } 
}

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>>;
}

impl<'b, B: Borrow<A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>> {
        self.get(name).map(|borrow| Box::new(borrow.borrow()))
    }
}

The error is:

error[E0308]: mismatched types
  --> src/main.rs:20:9
   |
20 |         self.get(name).map(|borrow| Box::new(borrow.borrow()))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait A, found &A
   |
   = note: expected type `std::option::Option<std::boxed::Box<dyn A + 'a>>`
              found type `std::option::Option<std::boxed::Box<&dyn A>>`

Which puzzles me, because I'd expect a &A to be an A too. I've tried to impl<'a> A for &'a A, but that doesn't help either. Is there any way to fix this?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Michail
  • 1,843
  • 1
  • 17
  • 21
  • `A` implementing a trait does not mean that `&A` does. It sometimes can seem like that's true because of Rust's auto-ref and auto-deref behaviour. For example, it will auto-deref a variable so you can type `foo.bar()` instead of `(*foo).bar()`. – Peter Hall Sep 16 '18 at 10:42
  • 3
    Why would want to return a `Box<&dyn A>` anyway? Boxing a reference doesn't seem very useful. – Peter Hall Sep 16 '18 at 10:53
  • I noticed that `name: &'a str` is unnecessarily constrained here, because the returned reference is not tied to `name`. Maybe you have other implementations of `ProducerOrContainer` where the returned value is derived from `name`, but if not, getting rid of the `'a` will be more flexible. – trent Sep 16 '18 at 13:06

2 Answers2

4

...that can either retrieve (and return a reference to) a trait object of another trait, or create one (and return a boxed version of it).

With this requirement, a Box will not work. A Box owns its data, but you sometimes have borrowed data, which you can't move.

There is a type in the standard library called Cow, which is an abstraction over whether a value is borrowed or owned. However, it may not be quite suitable for you here because it won't let you own the data as a Box and it also requires that your data type must implement ToOwned.

But we can take your requirement and model it directly as an enum:

enum BoxOrBorrow<'a, T: 'a + ?Sized> {
    Boxed(Box<T>),
    Borrowed(&'a T),
}

And make it ergonomic to use by implementing Deref:

use std::ops::Deref;

impl<'a, T> Deref for BoxOrBorrow<'a, T> {
    type Target = T;
    fn deref(&self) -> &T {
        match self {
            BoxOrBorrow::Boxed(b) => &b,
            BoxOrBorrow::Borrowed(b) => &b,
        }
    }
}

This lets you treat the custom BoxOrBorrow type as any other reference - you can dereference it with * or pass it to any function that expects a reference to T.

This is what your code would look like:

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>>;
}

impl<'b, B: Borrow<dyn A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>> {
        self.get(name)
            .map(|b| BoxOrBorrow::Borrowed(b.borrow()))
    }
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
3

You can make the original code compile by implementing A for &'_ dyn A and adding an explicit cast:

self.get(name).map(|borrow| Box::new(borrow.borrow()) as Box<dyn A>)

A closure is not a coercion site. The compiler looks at the contents of the closure to see what the return value is, and concludes that it returns Box<&'a dyn A>. But the closure itself cannot be coerced from "function returning Box<&'a dyn A>" to "function returning Box<dyn A + 'a>", because those types are structurally different. You add the cast to tell the compiler that you wanted the closure to return Box<dyn A> in the first place.

But this is a bit silly. Boxing a reference is completely unnecessary here, and casting it to Box<dyn A> just adds another level of indirection for the caller. It would be better to return a type that encapsulates the idea of "either a boxed trait object, or a reference to a trait object", as Peter Hall's answer describes.


In a future version of Rust, with generic associated types ("GATs"), it will be possible to make the return type an associated type of ProducerOrContainer, something like the following:

trait ProducerOrContainer {
    type Result<'a>: A;
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Result<'a>>;
}

With this trait definition, each type that implements ProducerOrContainer can choose what type it returns, so you can pick Box<dyn A> for some impls and &'a dyn A for others. However, this is not possible in current Rust (1.29).

trent
  • 25,033
  • 7
  • 51
  • 90