0

I am facing the following situation in Rust:

trait BaseTrait {
    fn do_base_stuff(&self) -> String;
}

trait GenericChildTrait: BaseTrait {}
trait DynamicChildTrait: BaseTrait {}

enum ChildTraitEnum<G: GenericChildTrait> {
    Generic(G),
    Dynamic(Box<dyn DynamicChildTrait>)
}

impl<G: GenericChildTrait> ChildTraitEnum<G> {
    fn as_base(&self) -> impl BaseTrait {
        match self {
            ChildTraitEnum::Generic(g) => g,
            ChildTraitEnum::Dynamic(d) => d
        }
    }
}

I have an enum that can assume multiple variants of children of the same parent trait. For reasons beyond the scope of this question, unfortunately, one of them has to be specified using a generic type. The others, however, can be dynamic trait implementations. I opted for the simplest mechanism, a simple Box<dyn DynamicChildTrait>, though I am open to changing the particular dynamic type descriptor in pursuit of solving the following problem:

I'm trying to implement an as_base() method on the enum whose sole purpose is avoiding match arms every single time there is a function call to a method that is implemented on the base trait.

So far, I've primarily tried the approach outlined here, as well as using approaches without implementing an AsBase trait. My approaches have essentially been variations of trying different return types of the as_base() method, such as impl BaseTrait, Box<dyn BaseTrait>, &BaseTrait, &dyn BaseTrait, &Box<dyn BaseTrait>, and, quite frankly, other rather embarrassingly aimless variants.

What follows is a little snapshot of various compilation errors that I encountered.

First off, here's what the current code produces:

error[E0308]: `match` arms have incompatible types
  --> src/signers.rs:17:43
   |
13 |   impl<G: GenericChildTrait> ChildTraitEnum<G> {
   |        - this type parameter
14 |       fn as_base(&self) -> impl BaseTrait {
15 | /         match self {
16 | |             ChildTraitEnum::Generic(g) => g,
   | |                                           - this is found to be of type `&G`
17 | |             ChildTraitEnum::Dynamic(d) => d
   | |                                           ^ expected type parameter `G`, found struct `Box`
18 | |         }
   | |_________- `match` arms have incompatible types
   |

Let's see what happens if we add an as_ref() to the second match arm:

error[E0277]: the trait bound `&dyn DynamicChildTrait: BaseTrait` is not satisfied
  --> src/signers.rs:14:26
   |
14 |       fn as_base(&self) -> impl BaseTrait {
   |                            ^^^^^^^^^^^^^^ the trait `BaseTrait` is not implemented for `&dyn DynamicChildTrait`
15 | /         match self {
16 | |             ChildTraitEnum::Generic(g) => g,
17 | |             ChildTraitEnum::Dynamic(d) => d.as_ref()
18 | |         }
   | |_________- return type was inferred to be `&dyn DynamicChildTrait` here

It seems impl wants the results to be of the same type, so let's use a dyn in the return type:

impl<G: GenericChildTrait> ChildTraitEnum<G> {
    fn as_base(&self) -> &dyn BaseTrait {
        match self {
            ChildTraitEnum::Generic(g) => g,
            ChildTraitEnum::Dynamic(d) => d.as_ref()
        }
    }
}

This produces a different error:

error[E0658]: cannot cast `dyn DynamicChildTrait` to `dyn BaseTrait`, trait upcasting coercion is experimental
  --> src/signers.rs:15:9
   |
15 | /         match self {
16 | |             ChildTraitEnum::Generic(g) => g,
17 | |             ChildTraitEnum::Dynamic(d) => d.as_ref()
18 | |         }
   | |_________^
   |

Discarding this approach for a second, let's try the one outlined in the other thread:

trait AsBaseTrait {
    fn as_base_trait(&self) -> &dyn BaseTrait;
}

impl<B: BaseTrait> AsBaseTrait for B {
    fn as_base_trait(&self) -> &dyn BaseTrait {
        self
    }
}

impl<G: GenericChildTrait> ChildTraitEnum<G> {
    fn as_base(&self) -> &dyn BaseTrait {
        match self {
            ChildTraitEnum::Generic(g) => g.as_base_trait(),
            ChildTraitEnum::Dynamic(d) => d.as_base_trait()
        }
    }
}

Even so, I still get a quite similar error:

error[E0599]: the method `as_base_trait` exists for reference `&Box<(dyn DynamicChildTrait + 'static)>`, but its trait bounds were not satisfied
   --> src/signers.rs:27:45
    |
6   |   trait DynamicChildTrait: BaseTrait {}
    |   ----------------------------------
    |   |
    |   doesn't satisfy `dyn DynamicChildTrait: AsBaseTrait`
    |   doesn't satisfy `dyn DynamicChildTrait: Sized`
...
27  |               ChildTraitEnum::Dynamic(d) => d.as_base_trait()
    |                                               ^^^^^^^^^^^^^ method cannot be called on `&Box<(dyn DynamicChildTrait + 'static)>` due to unsatisfied trait bounds
    |
   ::: /Users/arik/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:199:1
    |
199 | / pub struct Box<
200 | |     T: ?Sized,
201 | |     #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
202 | | >(Unique<T>, A);
    | | -
    | | |
    | |_doesn't satisfy `Box<dyn DynamicChildTrait>: AsBaseTrait`
    |   doesn't satisfy `Box<dyn DynamicChildTrait>: BaseTrait`
    |

I get the impression that I'm either missing something obvious, or trying to do something that Rust is really trying to tell me I shouldn't do. My currently planned "giving up" approach is simply implementing BaseTrait for ChildTraitEnum and doing all the matches there.

Another suggestion I have come across that I unfortunately cannot use due to constraints of the open-source project is using a feature for unstable upcasts. I also need to make sure the solution works on Rust versions as old as 1.48.

arik
  • 28,170
  • 36
  • 100
  • 156
  • 1
    I think you missed the essence of the `AsBaseTrait` approach you linked. Did you declare `trait BaseTrait: AsBaseTrait`? If you do that, it [should](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0c9559250e2fc6e25509c402b55cf32c) work. (Apologies if that changed since 1.48.) – Caesar May 16 '23 at 02:31
  • Thank you so much! I just tried that, and it does indeed work trivially. I'll close my question as soon as I get back from dinner. – arik May 16 '23 at 03:06
  • Yes, that's the exact question I linked originally, where you pointed out in your previous comment that I was missing the inheritance aspect. I have already voted to close my question as a duplicate of the referenced one. – arik May 17 '23 at 15:42
  • Ah, sorry. Since you said "close question" while I don't think you can do that, I voted to close as a duplicate and forgot to delete that stupid "Does this answer your question?" comment, which StackOverflow automatically adds on my behalf. – Caesar May 17 '23 at 22:53

0 Answers0