TL;DR: What's the accepted way to impl
a trait for all types implementing another trait in Rust?
I'm working through trying to understand Rust's type system and coming to a bit of a hangup. I've boiled the problem down, and realized that I'm basically just trying to translate the following thing I can write in Haskell to Rust:
class Wrapper s where
unwrap :: s t -> t
data Burrito t = Burrito t
instance Wrapper Burrito where
unwrap (Burrito inner) = inner
instance (Wrapper w, Show t) => Show (w t) where
show w = "(wrapped " ++ show (unwrap w) ++ ")"
main :: IO ()
main =
print . Burrito $ 1
I was able to generally translate this as follows:
trait Wrapper<T> {
fn unwrap(&self) -> T;
}
struct Burrito<T> { filling: T }
impl<T: Copy> Wrapper<T> for Burrito<T> {
fn unwrap(&self) -> T { self.filling }
}
impl<T: Display, W: Wrapper<T>> Display for W {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(wrapped {})", self.unwrap())
}
}
fn main() {
let burrito = Burrito { filling: 1 };
println!("{}", burrito);
}
But this errors on the T
type parameter in the Display impl
with error:
the type parameter
T
is not constrained by the impl trait, self type, or predicates unconstrained type parameter
The other alternative I tried was doing (as suggested by my IDE):
impl<T: Display> Display for dyn Wrapper<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(wrapped {})", self.unwrap())
}
}
but this now raises an error on the actual println in main (when done as println!("{}", burrito as Wrapper<i32>)
), that there is a cast to an unsized type.
Is there a canonical way to create an impl
for a trait (Display
here) for all types impl
ementing another trait (Wrapper<T>
here)?
impl<T: Display> Display for dyn Wrapper<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(wrapped {})", self.unwrap())
}
}
implements the Display
trait for a dyn Wrapper<T>
trait object. This is a type of object in Rust which uses dynamic dispatch instead of monomorphization. Because trait objects are dynamically sized, you can't store them on the stack. You can, however, keep a reference of type &dyn Wrapper<T>
:
fn main() {
let burrito = Burrito { filling: 1 };
println!("{}", &burrito as &dyn Wrapper<_>);
}
This is obviously a little more complicated than one would expect but there's a good reason. If you could impl<T: Display, W: Wrapper<T>> for W
you couldn't impl Display for Burrito<T>
anymore as that would conflict with the other impl and vice versa.