Difficulty with traits and trait objects

I am trying to implement a couple traits

One is a Server Trait that implements ping, the other is a Cluster of servers that should hold the Server trait objects

I have some stripped down sample code below.

use async_trait::async_trait;
use tokio::sync::{RwLock};
use std::sync::{Arc};

use std::collections::HashMap;
use std::fmt;

#[async_trait]
pub trait ServerCluster {
    type Error;

    // Add a server
    async fn add_server<T: Server + Send + Sync>(&self, server: T)
        -> Result<(), Self::Error>;
    
}

#[async_trait]
pub trait Server {
    type Error;

    // Unique id to identify the server
    fn id(&self) -> &str;
    // Ping server, returns status and message
    async fn ping(&self) -> Result<ServerPing, Self::Error>;

}

#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum ServerStatus {
    READY,
    NOTREADY,
    UNREACHABLE,
    MAINTENANCE,
}


#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct ServerPing {
    pub status: ServerStatus,
    pub message: String,
}


struct MyServerCluster{
    server_list:Arc<RwLock<HashMap<String, Box<dyn Server<Error = ServerError>>>>>,
}

#[async_trait]
impl ServerCluster for MyServerCluster{
    type Error = ServerClusterError;
    
    // Add a collection (espion server)
    async fn add_server<T: Server + Send + Sync>(
        &self,
        server: T,
    ) -> Result<(), Self::Error> {
        // Get a write lock on the server list
        let cluster_map = &self.server_list.write().await;

        cluster_map.insert(server.id().to_string(), Box::new(server));

        Ok(())
    }
    
}

struct MyServer {
    id:String
}

#[async_trait]
impl Server for MyServer {

    type Error = ServerError;

    // Unique id to identify the server
    fn id(&self) -> &str {
        self.id.as_str()
    }

    // Ping server, returns status and message
    async fn ping(&self) -> Result<ServerPing, Self::Error>{
        Ok(ServerPing{
            status: ServerStatus::UNREACHABLE,
            message: "Cant open connection".to_string()
        })
    }

}

#[derive(Debug, Clone)]
struct ServerError;

impl fmt::Display for ServerError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "There was an error")
    }
}

#[derive(Debug, Clone)]
struct ServerClusterError;

impl fmt::Display for ServerClusterError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "There was an error")
    }
}

fn main() {
    println!("Some stuff");
}

I have been messing with the HashMap type and the insert but I am struggling with the following error

error[E0271]: type mismatch resolving `<T as Server>::Error == ServerError`
--> src/main.rs:61:53
|
61 |         cluster_map.insert(server.id().to_string(), Box::new(server));
|                                                     ^^^^^^^^^^^^^^^^ expected struct `ServerError`, found associated type
|
= note:       expected struct `ServerError`
        found associated type `<T as Server>::Error`
= help: consider constraining the associated type `<T as Server>::Error` to `ServerError`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
= note: required for the cast to the object type `dyn Server<Error = ServerError>`

Can someone help me parse what this error is trying to tell me?

There seems to be an issue with the Error associated type but I am unsure what I need to change.

Is this the right approach to be using for traits or am I going about this wrong?

First off, thank you for providing self-contained code, that makes it so much easier to help you.

I think you want to change the ServerCluster::add_server signature to:

async fn add_server<T: Server<Error=Self::Error> + Send + Sync>(&self, server: T) -> Result<(), Self::Error>;

Notice the added constraint Error=Self::Error, which requires the server to have the same error type as the server cluster.

There's other compile errors that appear when I make that change, but I suspect that's the solution for this particular error.

1 Like

Hi Gretchenfrage, thanks for the help

I have updated the signature of add_server (and also changed some of the names to make things more clear for me in the error messages).

I am left with the below code

use async_trait::async_trait;
use tokio::sync::{RwLock};
use std::sync::{Arc};

use std::collections::HashMap;
use std::fmt;

#[async_trait]
pub trait ServerCluster {
    type Error;

    // Add a server
    async fn add_server<T: Server<Error=GenericServerError>  + Send + Sync>(&self, server: T)
        -> Result<(), Self::Error>;
    
}

#[async_trait]
pub trait Server {
    type Error;

    // Unique id to identify the server
    fn id(&self) -> &str;
    // Ping server, returns status and message
    async fn ping(&self) -> Result<ServerPing, Self::Error>;

}

#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum ServerStatus {
    READY,
    NOTREADY,
    UNREACHABLE,
    MAINTENANCE,
}


#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct ServerPing {
    pub status: ServerStatus,
    pub message: String,
}


struct MyServerCluster {
    server_list:Arc<RwLock<HashMap<String, Box<dyn Server<Error=Self::Error>>>>>,
}

#[async_trait]
impl ServerCluster for MyServerCluster{
    type Error = GenericServerClusterError;
    
    // Add a collection (espion server)
    async fn add_server<T: Server<Error=GenericServerError> + Send + Sync>(
        &self,
        server: T,
    ) -> Result<(), Self::Error> {
        // Get a write lock on the server list
        let cluster_map = &self.server_list.write().await;

        cluster_map.insert(server.id().to_string(), Box::new(server));

        Ok(())
    }
    
}

struct MyServer {
    id:String
}

#[async_trait]
impl Server for MyServer {

    type Error = GenericServerError;

    // Unique id to identify the server
    fn id(&self) -> &str {
        self.id.as_str()
    }

    // Ping server, returns status and message
    async fn ping(&self) -> Result<ServerPing, Self::Error>{
        Ok(ServerPing{
            status: ServerStatus::UNREACHABLE,
            message: "Cant open connection".to_string()
        })
    }

}

#[derive(Debug, Clone)]
struct GenericServerError;

impl fmt::Display for GenericServerError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "There was an error")
    }
}

#[derive(Debug, Clone)]
struct GenericServerClusterError;

impl fmt::Display for GenericServerClusterError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "There was an error")
    }
}

fn main() {
    println!("Some stuff");
}

which produces the following error

error[E0223]: ambiguous associated type
--> src/main.rs:46:69
|
46 |         server_list:Arc<RwLock<HashMap<String, Box<dyn Server<Error=Self::Error>>>>>,
|                                                                     ^^^^^^^^^^^ help: use fully-qualified syntax: `<MyServerCluster as Trait>::Error`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0223`.
error: could not compile `trait_test`.

This makes sense since I am setting Error=Self::Error and Self is a MyServerCluster

How (can?) I tell the server to use its Self::Error type (GenericServerError in this case)?

My end goal is to have the server cluster contain a hashmap of server trait objects that are not specifically the same type. Would it make sense to abandon this complication and have the hashmap contain only a single type of trait object (by having an Item type like the iterator trait?).

I think that a problem you're generally running into is that you want to store dyn Server objects with different associated Error types, but that won't work, the associated Server::Error type essentially makes it a different trait. That is to say, you can imagine like Server<Error=A> and Server<Error=B> are different traits, so you couldn't cast them both into a dyn Server. I think you're going to want to have a single, concrete error type which the servers return. For example, eyre provides this functionality.

1 Like

Yeah that makes sense. Thanks for all your help.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.