1

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 implementing another trait (Wrapper<T> here)?

5
  • 1
    Keeping the alternative impl you can do println!("{}", &burrito as &dyn Wrapper<_>); (note the &s), avoiding casting to an unsized type. Rust makes this hard to do because you might want to impl Display for Burrito<T> at some point which would conflict with the impl for Wrapper. Commented Oct 19, 2022 at 16:54
  • impl<T: Display, W: Wrapper<T>> Display for W would conflict with a normal impl Display for type W, if it already existed or was added later on. That kind of thing would also allow you to implement an external trait for an external type, just by going through a trait you own. Commented Oct 19, 2022 at 16:54
  • What are you actually trying to accomplish? Commented Oct 19, 2022 at 16:55
  • @isaactfa that seems to work for me - if you write it up as an answer, i'll happily accept it. Thanks! Commented Oct 19, 2022 at 17:22
  • @PitaJ - kind of mostly playing around with Rust to see what's possible & what isn't within the type system. Commented Oct 19, 2022 at 17:23

2 Answers 2

1
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.

Sign up to request clarification or add additional context in comments.

Comments

-1

As the error says, the type parameter T is not constrained enough. Rust wants that at least one of the following conditions to be met to use a generic type T in an impl block:

  • it must be used as a parameter (or as a parameter of a parameter, or ...) of the implementing type;
  • if you are implementing a trait, it's ok if it's a parameter of the trait;
  • it's bound as an associated type of the implementing type.

It's the last option that fits your need:

trait Wrapper {
    type Output;
    fn unwrap(&self) -> Self::Output;
}

struct Burrito<T> { filling: T }

impl<T: Copy> Wrapper for Burrito<T> {
    type Output = T;
    fn unwrap(&self) -> T { self.filling }
}

impl<T: Display, W: Wrapper<Output=T>> Display for W {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "(wrapped {})", self.unwrap())
    }
}

fn main() {
    let burrito = Burrito { filling: 1 };
    println!("{}", burrito);
}

See the playground.


As note by @isaactfa, this will not compile, but for a different reason. You cannot implement a foreign trait Display for any type W, no matter the constraints on W. This is called the orphan rule.

1 Comment

The Playground doesn't compile.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.