Search code examples
rusttraitsassociated-types

Is there a way to have a trait specify itself for an associated type in another trait that it extends?


I'd like to define a trait that has as supertrait another trait with its own trait object type as an associated type:

/// A trait for making things.
trait Make {
    type Output: ?Sized;
    fn make(self: Box<Self>) -> Box<Self::Output>;
}

/// A special case of Make, which makes things that impl the same trait.
trait Bootstrapper: Make<Output = dyn Bootstrapper> {} // Will NOT compile.

However, I can't do this because it creates an infinite cycle. In the example above, I would need to specify Output for dyn Bootstrapper, which is itself (dyn Bootstrapper). But then, I'd need to specify Output for that dyn Bootstrapper, and so on and so forth, e.g. Make<Output = dyn Bootstrapper<Output = dyn Bootstrapper<Output = dyn Bootstrapper<...>>>.

The Rust compiler seems to agree that this won't work:

error[E0391]: cycle detected when computing the supertraits of `Bootstrapper`
 --> src/lib.rs:8:1
  |
8 | trait Bootstrapper: Make<Output = dyn Bootstrapper> {} // Will NOT compile.
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: ...which again requires computing the supertraits of `Bootstrapper`, completing the cycle
note: cycle used when collecting item types in top-level module
 --> src/lib.rs:8:1
  |
8 | trait Bootstrapper: Make<Output = dyn Bootstrapper> {} // Will NOT compile.
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I also can't specify Output = Self because that would overly constrain the trait, so that a given implementation of Bootstrapper could only make() more of itself. I want a Bootstrapper to be able to make() other kinds of Bootstrapper. Please see this Rust playground for an example of (roughly) what I'm trying to do.

Is there a way to get around this, and have Bootstrapper specify Bootstrapper (not Self) for Output?


Solution

  • The solution I ended up with was this:

    /// A meta-`trait` that defines the associated types for a subset of `Make`.
    trait Traits {
        type Output : ?Sized;
    }
    
    /// A `trait` for making things. Takes in a set of `Traits` that define the
    /// output for the `make()` function.
    trait Make {
        type Traits : Traits;
        fn make(self : Box<Self>) -> Box<<Self::Traits as Traits>::Output>;
    }
    

    By separating out the associated types as a separate Traits meta-interface, I was able to implement Traits for a new concrete type and pass that in to Make:

    /// `Traits` implementation for `Bootstrapper`, which constrains the `Make`
    /// implementation for `Bootstrapper` to outputting a `Box<Bootstrapper>`.
    struct BootstrapperTraits;
    impl Traits for BootstrapperTraits {
        // NOTE: Specifying Self here gets around the circular dependency.
        type Output = dyn Bootstrapper<Traits = Self>;
    }
    
    /// A special case of Make that makes the same *kind* of thing as itself, but
    /// not *necessarily* `Self`.
    trait Bootstrapper : Make<Traits = BootstrapperTraits> {
        fn is_best(&self) -> bool;
    }
    

    The concrete type (BootstrapperTraits) is able to get around the circular supertraits dependency by specifying Self as the type Traits for Output. Then BootstrapperTraits is specified as the specific Traits implementation for Bootstrapper. Hence, anyone who implements Bootstrapper must now also implement Make<Traits = BootstrapperTraits>, which requires that Output is a dyn Bootstrapper.

    See this Rust playground for the full solution.