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
?
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 type
s 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.