Avoiding repeated `dyn` indirection

When you have a dyn-safe trait (e.g. io::Write), it's common practice to provide forwarding impls like impl<T: Trait + ?Sized> Trait for &mut T as possible given the method receivers. This means that you can write functions like fn f(out: impl Trait) once and use it for both monomorphic and dynamic dispatch, providing &mut dyn Trait for the generic parameter type.

There's a bit of a wrinkle when f wants to always use dynamic dispatch for some or all of its impl, though. When you do (&mut out) as &mut dyn Trait, you end up adding an additional extraneous layer of dynamic indirection for the extra syntax sugar of providing a monomorphic API around the internal dynamic dispatch.

Assuming the API can't just be written to directly use dyn Trait[1], what patterns do you use to avoid the accumulation of dyn indirection layers? The only option I can really think of is to add a method fn as_dyn(&mut self) -> &mut dyn Trait; unfortunately, this needs to be manually implemented by every trait impl if it's going to be provided by dyn Trait, as it's impossible to provide a default method body that will work for Self: ?Sized.

What I've ended up doing the couple times I ran into this was make the dyn Trait-taking API pub(crate) such that when higher-level batch API delegates to the lower level API it doesn't end up stacking more indirection beyond the first level unnecessarily.


  1. One notable potential reason would be utilizing where Self: Sized functionality, or even just the ability to inline some generic setup around the core dynamically dispatched computation. Another is that the dyn trait is actually a supertrait, with the generic API taking a non-dyn-safe subtrait. ↩︎

1 Like

If you change a function from taking impl Trait by ownership to instead always take a mutable reference, then you can often avoid creating additional dyn references on each call.

I believe you can avoid having to implement it if you put it on a helper trait Helper such that Helper is blanket implemented for any T: Trait and Helper is a supertrait of Trait.

3 Likes

I second this. It's useful in many dyn scenarios where you want a default body for T: Sized and want the compiler to supply the implementation for dyn Trait. Here's a recent example.[1]


  1. from this thread ↩︎

1 Like

I don't see why DebugAny needs AsAny in that example. What am I missing?

Ah yes, that's clever. And you can even hide this by making a Trait method default delegate to the Helper implementation. That probably isn't the best idea since impl library::Trait for dyn crate::Trait will expose the trickery, but it's definitely a cute trick.

Such that dyn DebugAny: AsAny. We have an implication from DebugAny + Sized to AsAny, but we need DebugAny + ?Sized to AsAny.

At least if you're putting the dyn through generic API. If you hit explicitly polymorphic API, to turn &mut impl Trait + ?Sized into &mut dyn Trait you need to add another reference, because you can't unsize an already unsized value.

Yes, but it isn't needed for the functionality of the function in DebugDyn. That trait is a but confusingly named I believe.

Maybe the linked version wasn't the best example, but the pattern does come up a lot.

Basically whenever you want a default body that requires Sized or some other non-dyn-capable bound, but you want the method to remain dyn-capable (so you can't add the bound to the method).


  1. or Arc<dyn Trait> or Rc<dyn Trait> (so far) ↩︎

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.