Boxed trait object doesn't impl Trait?

#1

Hi,

Yesterday I was playing a little with Rust, and there is one think, which surprised me - Box<dyn MyTrait> does not impl MyTrait. I would like to pass it to function like fn foo(arg: impl MyTrait), and have dynamic dispatch. Firstly I though, that it would be somehow impossible to implement, but then I came with something like:

impl MyTrait for Box<dyn MyTrait> {
  fn method(&self) {
    let t: &dyn MyTrait = self;
    *self.method();
  }
}

Probably it could be inlined with some fancy as, but it wasn’t my intention. I understand, it is impossible to express something like impl T for Box<dyn T>, but my intuition point, that if something impl Deref<Target=dyn Trait>, compiler could silently implement this trait and I don’t see any downsides of it. For now I don’t see any clear way of passing dynamicly dispatched object to function expecting one implementing a Trait. The question is: what am I missing (in particular: why this auto-implementing trait bad idea), and what is the simplest solution to achieve mixing static and dynamic dispath?

1 Like
#2

Try passing it to a function like fn foo(arg: &dyn MyTrait). impl Trait in argument position actually does static dispatch IIRC.

#3

This one is obvious for me, but in general I don’t control extern crates functions signatures, and they commonly takes arguments as impl Trait (or actually what is more common by generic trait bounds, but on this level it is pretty much the same). In particular I don’t see any reason, why Box cannot perform this static dispatch and under the hood do dynamic one.

#4

I see, I mis-understood. The adapter trait implementation you provided could probably be automated via some kind of procedural macro, but that’s an area I have no experience with. Perhaps a #[derive(...)] macro for traits?

Often Rust likes to be explicit, so some may argue against your proposition because it isn’t explicit whereas a macro would be. If one is developed I think it would be worthy to place in std.

I ran into a similar situation involving Box<dyn Error> a while ago and I solved it with the Borrow trait. I don’t have access to that code right now, otherwise I would post it here, so I’m not sure if it exactly applies.

#5

Custum #[derive(...)] would need to be created per-trait (I don’t think, that it is possible to access derived trait fields in custom derive implementation), so it will be probably too much boilerplate. Also it would be done on bad side - I don’t want crate creator to think if maybe his function will be called with type which cannot be infered in compile time, I want this responsibility to be on crate user (because from my experience its actually pretty rare case). Adapter derive is a way I handle it for now, but it seems as it may be automated on compiler level. For now I am using even more generic solution:

impl<T: Deref<Target=MyTrait>> MyTrait for T {
  fn method(&self) { self.deref().method() }
}

But its still boilerplate I want to avoid. I am thinking about creating RFC, but firstly I want to know if I don’t miss something obvious.

Also there is another use case - sometimes I want to create a function, which I want to use with type known at compile time, but in 1% cases I would like to use dynamic dispatch (beacuse type is selected conditionally). Obviously I may just go with fn foo(arg: &dyn MyTrait) and it will work both with “normal” object and with trait objects, but It forces all uses of the function to perform dynamic dispatch which is just waste (and not conforming to “do not pay what you don’t use” rule).

#6

I also thought about this sometimes.
I don’t see, why automatically implementing a method for a dynamic trait, that is implemented for specialized traits, would be a problem.
The only thing I can think of is, that someone calls a dynamic version by accident, when wanting to call a static version, but since one has to explicitly create a dynamic trait object, this shouldn’t be a problem.
It would be a breaking change, since some traits might implement the dynamic calls to the method in a different way.
Maybe default impl/specialization will help there.

#7

I dont see how it would be breaking change. I dont want Box<T> to implement all traits T implement, I am talking about specific sytuation, when T is dyn Trait when I want only this specific Trait to be implemented (or more generic implement Trait for every type which implements Deref with Target=dyn Trait).

Implementing every trait T implements for Box<T> would actually break things. The problem is, what if some trait has method fn foo(&self), and T has its own fn foo(&self) - there is no clear way to decide which one to call with t.foo() - to preserve backward compatibility it should be t.deref().foo(), but the one from trait is intuitively closer (it is defined for Box directly, not need to deref anything). But this is something I don’t know for that specific reason.

Obviously even in my idea, methods which are defined directly on box, are not accessible directly (so if MyTrait has method fn leak(self) -> &'static mut dyn MyTrait, then Box::<dyn MyTrait>::leak() calls Box::leak, not MyTrait::leak, but it is both backward compatible, and logical (it comes from standard rules).

#8

The problem is, someone may already have implemented it:

impl MyTrait for Box<dyn MyTrait> {
    fn method(&self) {
        panic!("This method cannot be called for dynamic traits");
    }
}
1 Like
#9

Yes, this one is something which would require specialization if it will be implemented as implicit trait specialization. Actually I have it in many places and I still missed this argument.

#10

Actually, specialization can’t help you here, since you want the exact same impl to be generated as what can already exist. This is something specialization will not cover.

#11

I’d just like to point out that even just the implicit impl for dyn Trait itself has created a soundness hole by potentially overlapping with blanket impls.

1 Like
#12

On its own not, but I mean that specialization is required for such change to not be breaking one.