The trait `InterServiceClient` cannot be made into an object

I'm trying to create a wrapper around reqwest::Client. I created the " InterServiceClient " trait and implemented it using the "InterServiceClientImpl" struct. One of the "InterServiceClient" methods looks like below.

async fn post<R: DeserializeOwned + Send + Sync>(
        &self,
        url: &str,
        headers: Vec<Header>,
    ) -> Result<R, reqwest::Error>;

and I wrote a method to get the InterServiceClient.

pub fn build_interservice(base_url: String) ->  Box<dyn InterServiceClient> {
    Box::new(InterServiceClientImpl::build(base_url))
}

But I'm getting the below error from the above method.

the trait InterServiceClient cannot be made into an object
consider moving post to another trait
only type utils::interservice::InterServiceClientImpl is seen to implement the trait in this crate, consider using it directly instead
InterServiceClient can be implemented in other crates; if you want to support your users passing their own types here, you can't refer to a specific type
required for the cast from Box<InterServiceClientImpl> to Box<(dyn InterServiceClient + 'static)>
Need help to solve this issue.

Update

This is my code looks like

pub fn build_interservice(base_url: String) ->  Box<dyn InterServiceClient> {
    Box::new(InterServiceClientImpl::build(base_url))
}

#[async_trait]
pub trait InterServiceClient: InterServiceClientClone + Send {
    async fn post<R: DeserializeOwned + Send + Sync>(
        &self,
        url: &str,
        headers: Vec<Header>,
    ) -> Result<R, reqwest::Error>;


}

#[derive(Clone)]
struct InterServiceClientImpl {
    client: reqwest::Client,
    base_url: String,
}

impl Clone for Box<dyn InterServiceClient> {
    fn clone(&self) -> Box<dyn InterServiceClient> {
        self.clone_box()
    }
}

pub trait InterServiceClientClone {
    fn clone_box(&self) -> Box<dyn InterServiceClient + Send + Sync>;
}

impl InterServiceClientClone for InterServiceClientImpl {
    fn clone_box(&self) -> Box<dyn InterServiceClient + Send + Sync> {
        Box::new(self.clone())
    }
}

#[derive(Debug, Clone)]
pub struct Header {
    header: String,
    value: String,
}

impl Header {
    pub fn build(header: String, value: String) -> Self {
        Header { header, value }
    }
}

impl InterServiceClientImpl {
    pub fn build(base_url: String) -> Self {
        let client: reqwest::Client = reqwest::Client::new();
        InterServiceClientImpl { client, base_url }
    }
}

#[async_trait]
impl InterServiceClient for InterServiceClientImpl {
    async fn post<R: DeserializeOwned + Send + Sync >(
        &self,
        url: &str,
        headers: Vec<Header>,
    ) -> Result<R, reqwest::Error> {
        let builder = self
            .client
            .post(format!("{}/{}", self.base_url, url))
            // .json(body)
            .header("Content-Type", "application/json");

        let builder = headers.iter().fold(builder, |builder, header| {
            builder.header(&header.header, &header.value)
        });

        let result = builder.send().await;
        match result {
            Ok(response) => {
                debug!("Response from server : {:?}", response);
                let result = response.json::<R>().await?;
                Ok(result)
            }
            Err(error) => {
                error!("Error invoking api {:?}", error);
                Err(error)
            }
        }
    }

}

Could you share the reproduction code, particularly the definition of InterServiceClient? I've tried to build the minimized version of what I think is the error, but it yielded an unrelated syntax error.

(On mobile)

The two problems are

  • async method
    • fix: return a pinned box future
  • type generic method
    • maybe a fix?: erased serde crate

I added the entire code to the original question.

Where is this attribute coming from?

async_trait. It exists to apply the pinned-box-future pattern.

Here's a way to separate the generic method:

  • have implementors just return the request::Response instead of R
  • have a non-dyn-dispatchable method be the generic, deserializing version
  • add an inherent method to dyn InterServiceClient + '_ to do the same thing
#[async_trait]
pub trait InterServiceClient: InterServiceClientClone + Send {
    async fn post<R: DeserializeOwned + Send + Sync>(
        &self,
        url: &str,
        headers: Vec<Header>,
    ) -> Result<R, reqwest::Error>
    where
        Self: Sized,
    {
        self.post_response(url, headers)
            .await?
            .json::<R>()
            .await
    }

    async fn post_response(
        &self,
        url: &str,
        headers: Vec<Header>,
    ) -> Result<reqwest::Response, reqwest::Error>;
}
impl dyn InterServiceClient + '_ {
    pub async fn post<R: DeserializeOwned + Send + Sync>(
        &self,
        url: &str,
        headers: Vec<Header>,
    ) -> Result<R, reqwest::Error> {
        self.post_response(url, headers)
            .await?
            .json::<R>()
            .await
    }
}

Caveats:

  • I didn't test it, just got it compiling
  • I didn't attempt to get post ... where Self: Sized into non-type-erased form (no #[async_trait]) but it's probably possible
  • (side note) erased_serde wasn't the answer

Eh, this runs into "ambiguity" due to this issue. You can work around that:

  • Don't have the trait method
  • Don't have the dyn inherent method, and impl ISC for Box<dyn ISC + '_>
  • Keep both but give them different names

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.