Embedding Trait inside of Struct

A simplified version of your problem is here:

trait Foo {
    fn stuff (&self)
    ;
}

fn makes_foo () -> impl Foo
{
    struct Struct;
    impl Foo for Struct {
        fn stuff (self: &'_ Self)
        {
            eprintln!("<impl Foo>::stuff()");
        }
    }
    Struct
}

struct FooContainer<T : Foo> {
    field: T,
}

impl<T : Foo> FooContainer<T> {
    fn new () -> Self
    {
        Self {
            field: makes_foo(),
        }
    }
    
    fn other_stuff (self: &'_ Self)
    {
        self.field.stuff();
    }
}

There are three ways to "solve" this:

  1. "Nameable" existential types
    This is currently only possible in nightly, as it requires a feature that lets you "name" an existential type by defining a type ... = ... alias:

    #![feature(type_alias_impl_trait)]
    
    type makes_foo_Ret = impl Foo;
    /// say that makes_foo_Ret is the return type of our own `makes_foo`:
    fn our_own_makes_foo () -> makes_foo_Ret { makes_foo() }
    
    struct FooContainer { // no longer generic!
        field: makes_foo_Ret,
    }
    
    impl FooContainer { // no longer generic!
        ... // use our_own_makes_foo() instead of makes_foo()
    
  2. Box<dyn ...>-it up!
    This one implies the runtime cost of an extra allocation, but also manages to get rid of the generic, and it works on stable Rust.
    Basically instead of type makes_foo_Ret = impl Foo one needs to use = Box<dyn Foo>, and our_own_makes_foo body needs to Box::new() the return value of makes_Foo.
    Finally we can just inline these two definitions into their usage site:

    struct FooContainer {
        field: Box<dyn Foo>,
    }
    
    impl FooContainer {
        fn new () -> Self
        {
            Self {
                field: Box::new(makes_foo()),
            }
        }
    
  3. :shushing_face:Don't name it!:shushing_face:

    This means that you will never have a nameable exterior type, you will just use impl Foo in the return type. So to even be able to define the struct, we need it to be generic over all Ts that implement Foo, even if in practice only one specific type will be used. And to construct the "lucky one", a simple function suffices:

    struct FooContainer<T : Foo> {
        field: T,
    }
    
    fn FooContainer_new() -> FooContainer<impl Foo>
    {
        FooContainer {
            field: makes_foo(),
        }
    }
    
    impl<T : Foo> FooContainer<T> {
        fn other_stuff (self: &'_ Self)
        {
            self.field.stuff();
        }
    }
    

    But that constructor does not look pretty.

    • Some hacks to prettify that specific constructor

      We try with:

      struct FooContainer<T : Foo> {
          field: T,
      }
      
      impl<T : Foo> FooContainer<T> {
          fn new () -> FooContainer<impl Foo> // != Self
          {
              FooContainer /* != Self */ {
                  field: makes_foo(),
              }
          }
      ...
      

      So that we have a function FooContainer::new() -> FooContainer<impl Foo>.

      Except that it's not just one function. If we have T : Foo and U : Foo, then we have two officially different functions that actually do the same: FooContainer::<T>::new() and FooContainer::<U>::new(). And even if all these functions are equivalent, and even if only one single type in the whole universe implements Foo, the call FooContainer::new() will be ambiguous to Rust (it will not know which type to use to monomorphize, because the type is not used and thus not constrained). Which leads to an inference error.

      The solution? Either make it possible to provide a type, even if a dummy one, such as T = ():
      impl FooContainer<()> { fn new () -> FooContainer<impl Foo> { ... (removing the T : Foo bound on the struct definition). And that works. But honestly, () had no role here. So, if we are to use an absurd type here, let's at least pick one that "reads well", such as dyn Foo:

      struct FooContainer<T : Foo + ?Sized> {
          field: T,
      }
      
      impl FooContainer<dyn Foo> {
          fn new () -> FooContainer<impl Foo> { ...
      

      But then, it requires a ?Sized bound on T, some people may (rightfully) imagine / think that dynamic dispatch is involved (when it isn't, we are only using impl Trait here), so it may be more disorienting than ().

      So here is my suggested hack: use something like (), but with a more suitable name, and make it so it is impossible to accidentally explicitely access that "hack" type (only reachable from Rust type inference):

      mod private {
          use super::*;
      
          pub
          enum implFoo {}
      
          impl FooContainer<implFoo> {
              pub
              fn new () -> FooContainer<impl Foo> // != Self
              {
                  FooContainer /* != Self */ {
                      field: makes_foo(),
                  }
              }
          }
      }
      
      • Playground

      • Explanation: the pub item implFoo is "hidden" within a private module so it is effectively unnameable, much like the any impl Foo existential type, and yet despite it being hidden it is technically accessible from everywhere (pub) so you can "reach" for it through type inference, which is unambiguous.

      • Here is a preview of the generated docs:

2 Likes