Specifying associated type in trait that inherits from another trait

不问归期 提交于 2021-01-27 15:01:37

问题


I started working on my first more ambitious Rust project, and struggle with something I did not come across in any of the resources and tutorials I used for learning. The title of the question captures the abstract problem, but for the examples I'll use the concrete examples I am fighting with.

For my project, I need to interface with different third-party services, and I decided to use the actix framework as an abstraction for the different actors in my domain. The framework defines the Actor trait that must be implemented:

use actix::prelude::*;

struct MyActor {
    count: usize,
}

impl Actor for MyActor {
    type Context = Context<Self>;
}

I have a trait of my own that defines the interface for the third-party integrations. Let's call it Client. I want each client to behave like an actor.

use actix::Actor;

pub trait Client: Actor {}

Somewhere else I have a vector that stores references to all the active clients in the system. When I compile the code, I get the following error:

error[E0191]: the value of the associated type `Context` (from the trait `actix::actor::Actor`) must be specified
  --> transponder/src/transponder.rs:15:26
   |
15 |     clients: Vec<Box<Client>>
   |                      ^^^^^^ missing associated type `Context` value

I spent a few hours now attempting to solve this problem, but none of them worked.

  • I tried to specify the type in the trait, but got associated type defaults are unstable as an error.
  • I tried to specify the type in the trait's implementation (impl Simulation), but got associated types are not allowed in inherent impls as an error.
  • I tried some stuff with impl <T: Actor> Simulation for T (e.g. as shown here), but nothing worked.

My assumption is that I am missing an important piece of knowledge about traits and types. I would be very grateful if someone could help me solve my problem, and point me in the direction of the missing puzzle piece. I feel there is an important lesson about Rust in here that I really want to learn.


回答1:


The signatures for all methods of all items in a collection must be identical, so that you can use them interchangeably. This means that the associated types of each item must be the same too.

You can get rid of this error by providing a concrete type for the Context associated type:

Vec<Box<dyn Client<Context = Context<MyActor>>>>

However, the code still won't work because Actor has a bound of Self: Sized, which means it can't be made into a trait object, so neither can your trait which extends it.




回答2:


As long as i understand from your code your main goal is add all Client/Actors in a collection and call it's common behavior when its needed.But due to Object Safety its not possible so we can do it with a little hack( creating trait which mimics Client, i named it as ClientProxy).

I have a trait of my own that defines the interface for the third-party integrations. Let's call it Client. I want each client to behave like an actor.

pub trait Client: Actor {}

Yes this works like that, actually this means If some struct has an implementation of Client it needs to have Actor's implementation too.

Consider we have two Context MyActor and OtherActor and their Client/Actor implementations. And we have a behavior in Client ( behave_like_client(&self) ).

pub trait Client: Actor {
    fn behave_like_a_client(&self);
}

struct MyActor {
    count: usize,
} 

impl Actor for MyActor {
    type Context = Context<Self>;
}
impl Client for MyActor {
    fn behave_like_client(&self) {
        println!("I am MyActor as Client, and my count is {}", self.count);
    }
}

struct OtherActor {
    count: usize,
}

impl Actor for OtherActor {
    type Context = Context<Self>;
}
impl Client for OtherActor {
    fn behave_like_client(&self) {
        println!("I am OtherActor Client, and my count is {}", self.count);
    }
}

Now we have our Actors for testing but let's go back to our problem Object Safety, we can't collect these Clients into a single collection. That's why i created ClientProxy to mimic Client and i want to implement ClientProxy on Clients. We can do this with implementing ClientProxy on Generics which extends Clients:

//ClientProxy must have all behaviors in Client 
trait ClientProxy {
    fn behave_like_client(&self);
}

//This code implements ClientProxy to all Client like Objects
impl<T> ClientProxy for T
where
    T: Client,
{
    fn behave_like_client(&self) {
        self.behave_like_client();
    }
}

Now everything is ready, we can test our structure:

struct Container {
    clients: Vec<Box<ClientProxy>>,
}

fn main() {
    let mut container = Container {
        clients: Vec::new(),
    };
    let a = Box::new(MyActor { count: 3 });
    let b = Box::new(OtherActor { count: 4 });

    container.clients.push(a);
    container.clients.push(b);

    container
        .clients
        .iter()
        .for_each(|a| a.behave_like_client());
    //output : 
    //I am MyActor as Client, and my count is 3
    //I am OtherActor Client, and my count is 4
}

You can reach the full code from playground ( it's not running on playground due to lack of dependency )



来源:https://stackoverflow.com/questions/53805212/specifying-associated-type-in-trait-that-inherits-from-another-trait

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!