Embedding Trait inside of Struct

I'm fairly new to rust and have been trying to learn by actually creating simple websites.

I was able to embed a trait inside of a struct. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cb94e9a63d94e9a6cbc9deb6a1d34acc

use std::env;

pub trait BlogBackend {}

pub struct AppState {
    pub host: String,
    pub port: String,
    pub blog: Box<dyn BlogBackend + Send + Sync>,
}

pub struct BlogFsBackend{}

impl BlogBackend for BlogFsBackend{}

impl  AppState {
    pub fn new() -> AppState {
        AppState {
            host: env::var("HOST").unwrap_or("127.0.0.1".to_string()),
            port: env::var("PORT").unwrap_or("8080".to_string()),
            blog: Box::new(BlogFsBackend{}),
        }
    }

    pub fn addr(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }
}

Here is how I'm using app state with tide.

pub mod appstate;

use appstate::AppState;

pub async fn start() -> Result<(), std::io::Error> {
    let state = AppState::new();
    let addr = state.addr();
    let mut app = tide::with_state(state);
    app.at("/").get(|_| async move { "Hello world!" });
    app.listen(addr).await?;
    Ok(())
}

Here is tide's with_state contract.

pub fn with_state<State>(state: State) -> server::Server<State>
where
    State: Send + Sync + 'static,
{
    .....
}
  1. Is Box<dyn trait> the way to embed trait inside of a struct? Are there other alternatives?
  2. I never implemented Send + Sync for BlogFsBackend but somehow rust seems to auto implement it since as soon as I remove Send + Sync from Box<dyn BlogBackend + Send + Sync> it fails with compilation error since with_state requires it. Can someone explain how this works.
  3. Is it preferred to use String for host in struct or &str?

There are two ways to embed a trait inside a struct: boxing and generics.

With boxing, the trait object will be heap allocated, and the compiler doesn't know which concrete implementation of the trait you're using. Note that this missing knowledge is why you need to tell it about Send and Sync: the compiler forgot what the underlying type was, so it can't check if it's Send or not unless you require that the box always contains something that's Send.

With generics you don't need to tell it if it's Send, because the actual implementation is part of the type containing the trait in the generic parameter. Thus it can check if it is Send in that specific case. However this means that different implementations of the trait results in different types for the container, and you can't just swap out the implementation at runtime. (that would change the type)

You should probably use String for host. Using &str requires that the string is owned somewhere else than in your struct, so the compiler will be using lifetimes to check that you don't destroy whatever is storing the string before you destroy the AppState.

2 Likes

For completeness, here is an example showing what using a generic <Blog> type would look like:

use ::std::env;

pub
trait BlogBackend {
    // ...
}

pub
struct AppState<Blog>
where
    Blog : BlogBackend + Send + Sync + 'static,
{
    pub host: String,
    pub port: String,
    pub blog: Blog,
}

#[derive(Default)]
pub
struct BlogFsBackend {
    // ...
}

impl BlogBackend for BlogFsBackend {
    // ...
}

impl AppState<Blog>
where
    Blog : BlogBackend + Send + Sync + 'static,
{
    pub
    fn new () -> Self
    where
        Blog : Default, // or provide an explicit `blog: Blog` argument to initialize Self with.
    {
        AppState {
            host: env::var("HOST").unwrap_or("127.0.0.1".into()),
            port: env::var("PORT").unwrap_or("8080".into()),
            blog: Blog::default(),
        }
    }

    pub
    fn addr (&self) -> String
    {
        format!("{}:{}", self.host, self.port)
    }
}

and then use it as:

use ::std::io;

use appstate::{AppState, BlogFsBackend};
mod appstate;

pub
async fn start () -> Result<(), io::Error>
{
    let state = AppState::<BlogFsBackend>::new(); // provide a concrete Blog
    let addr = state.addr();
    let mut app = tide::with_state(state);
    app.at("/").get(|_| async move { "Hello world!" });
    app.listen(addr).await?;
    Ok(())
}
1 Like

Seems like I was so much focused on fighting with the borrower checker that I completely forget about generics. Here I do prefer generics for my use case. I also modified so that AppState::new(&blog) now takes a blog reference.

use crate::blog::BlogBackend;
use std::env;

pub struct AppState<'a, Blog>
where
    Blog: BlogBackend + Send + Sync,
{
    pub host: String,
    pub port: String,
    pub blog: &'a Blog,
}

impl<'a, Blog> AppState<'a, Blog>
where
    Blog: BlogBackend + Send + Sync,
{
    pub fn new(blog: &'a Blog) -> AppState<'a, Blog> {
        AppState {
            host: env::var("HOST").unwrap_or("127.0.0.1".to_string()),
            port: env::var("PORT").unwrap_or("8080".to_string()),
            blog: &blog,
        }
    }

    pub fn addr(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }
}

If I use this let state = AppState::<BlogFsBackend>::new(&BlogFsBackend {}); everything seems to compile and run fine but as soon as I use let state = AppState::<BlogFsBackend>::new(&BlogFsBackend::default()); it says temporary value is freed at the end of the statement. How can I still use default() and satisfy the lifetime scope given the above code.

The above code was my first instinction on using generics with AppState coming from other languages and limited knowledge of rust lifetimes.

If I do remove pub blog: &'a Blog, reference here and use pub blog: Blog, it would work since now blog would have the same lifetme as AppState. Here is what I landed but I'm curious if the above example would make sense and if there is a solution to using default or is not rust like.

use crate::blog::BlogBackend;
use std::env;

pub struct AppState<Blog>
where
    Blog: BlogBackend + Send + Sync + 'static,
{
    pub host: String,
    pub port: String,
    pub blog: Blog,
}

impl<Blog> AppState<Blog>
where
    Blog: BlogBackend + Send + Sync + 'static,
{
    pub fn new(blog: Blog) -> AppState<Blog> {
        AppState {
            host: env::var("HOST").unwrap_or("127.0.0.1".to_string()),
            port: env::var("PORT").unwrap_or("8080".to_string()),
            blog: blog,
        }
    }

    pub fn addr(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }
}

As for String host it does make sense to not use &str since someone else would need to own. Primary reason for it was I wanted it to be immutable once AppState has been constructed but the better example here would be to make both host and port private and only expose addr as public so it can't change once AppState is constructed.

Thanks for the help. Learned quite a lot.

In this case you should prefer Blog to &'a Blog for exactly the same reasons you should prefer String to &'a str. As for immutability, you can make the fields non-public and provide a host method that returns &str to the inner string.

1 Like

Thanks again. Finally it makes sense.

In general when deciding between owned and borrowed types, you should choose based on whether you need an owned version, not whether you need it to be immutable or not. Generally immutability is enforced by just not giving out &mut references to the fields (and not making them public).

1 Like

In Rust binding an expression to a variable name does have a semantic difference (it affects when a local is dropped). To make your blog long lived, you'll thus need to bind it to a local:

let blog = BlogFsBackend::default();
let state = AppState::new(&blog);

But as @alice said, you will notice that since state borrows from blog, you will not be able to move blog out of the function, which can be unergonomic. You will also notice that many runtime functions such as tide's will complain about your thing not being 'static, which is another way of saying: "your are borrowing from a local; such thing is too inconvenient for me to use properly and soundly".

Hence the preference for owned values :wink:

1 Like

It's worth remembering here that in struct Blah<T> { ... }, the T is generic over all types and given that &Concrete is just another type, you can still construct Blah<&'c Concrete> instances with borrowed data. The only reason to use struct Blah<'t, &'t T> it if you intend for Blah to always act as a 'view' over some T. Usually this is not needed, but it makes sense for things like iterators and slices... basically anywhere that your struct needs reference semantics to function.

2 Likes

So now I'm trying another project with similar issue of storing generics impl in struct and since it returns impl Trait I can't seem to figure out how to add it to the sturct. I'm using termwiz 0.6.0.

I would like to store BufferedTermainal<T> inside Editor struct.

use anyhow::Error;
use termwiz::caps::Capabilities;
use termwiz::terminal::buffered::BufferedTerminal;
use termwiz::terminal::{new_terminal, Terminal};

pub struct Editor<T: Terminal> {
    buffer: BufferedTerminal<T>,
}

impl<T: Terminal> Editor<T> {
    pub fn new() -> Result<Editor<T>, Error> {
        let caps = Capabilities::new_from_env()?;
        let mut terminal = new_terminal(caps)?;
        let buffer = BufferedTerminal::new(terminal)?;
        Ok(Editor { buffer })
    }
}

The problem here is new_terminal returns Result<impl Terminal, Error> I can't get the actual generic type. Here seems like this is a use case where I would use Box with dynamic dispatch but can't seem to figure out.

Here are some relevant snippets from the termwiz source code.

pub struct BufferedTerminal<T: Terminal> {
    terminal: T,
    ...
}

pub fn new_terminal(caps: Capabilities) -> Result<impl Terminal, Error> {
    SystemTerminal::new(caps)
}

Here is the direct link to the source code of termwiz. https://github.com/wez/wezterm/tree/152aa85e8217c39e74ea920a913140879373ed63/termwiz

Also how would one implement Editor without generics.

It should be possible to use impl Terminal in the return type of your function. That said, you can definitely put it in a box instead. Putting it in a box is also how you erase the generic type.

Here is what I tried and it will fail with doesn't have size known at compile-time

pub struct Editor {
    buffer: Box<BufferedTerminal<Terminal>>
}

Boxing Terminal doesn't work either as it fails with Terminal is not implemented for std::box ...

pub struct Editor {
    buffer: BufferedTerminal<Box<dyn Terminal>>
}

The only way I have got it to compile is with generic but then it requires knowing the size which new_terminal doesn't return.

pub struct Editor<T: Terminal> {
    buffer: BufferedTerminal<T>
}

Is this more of an issue with the library?

You want the box around the trait. Box<dyn Terminal>. If Terminal isn't implemented for the box, then that's a bit annoying, but you can do this:

struct MyTerminalBox {
    inner: Box<dyn Terminal>,
}

impl Terminal for MyTerminalBox {
    fn set_raw_mode(&mut self) -> Result<(), Error> {
        self.inner.set_raw_mode()
    }
    fn set_cooked_mode(&mut self) -> Result<(), Error> {
        self.inner.set_cooked_mode()
    }
    fn enter_alternate_screen(&mut self) -> Result<(), Error> {
        self.inner.enter_alternate_screen()
    }
    fn exit_alternate_screen(&mut self) -> Result<(), Error> {
        self.inner.exit_alternate_screen()
    }
    ...
}

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:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.