1

I am running into the error "the method cannot be invoked on a trait object" with a separate trait implementation.

This is my Minimal Reproducible Example:

Cargo.toml

[package]
name = "mrp"
version = "0.1.0"
authors = ["Empty2k12"]
edition = "2018"

[dependencies]
futures = "0.1.27"
tokio = "0.1.20"
serde = { version = "1.0.92", features = ["derive"] }
serde_json = { version = "1.0" }

main.rs

use serde::{Deserialize, Serialize};

pub mod integrations {
    pub mod serde_integration;
}

struct MyDbClient {}

#[derive(Serialize, Deserialize, Debug)]
pub struct Weather {
    temperature: i32,
}

#[cfg(test)]
mod tests {
    use super::Weather;
    use super::MyDbClient;
    use crate::integrations::serde_integration::MyDbSerdeORM;

    #[test]
    fn mrp() {
        let weather = Weather { temperature: 82 };

        MyDbClient {}.json_query::<Weather, ToString>(serde_json::to_string(&weather).unwrap())
    }
}

integrations/serde_integration.rs

use serde::de::DeserializeOwned;

use super::super::MyDbClient;

use futures::Future;

pub trait MyDbSerdeORM {
    fn json_query<T: 'static, Q>(self, q: Q) -> Box<dyn Future<Item = Option<T>, Error = ()>>
    where
        Q: ToString,
        T: DeserializeOwned;
}

impl MyDbSerdeORM for MyDbClient {
    fn json_query<T: 'static, Q>(self, q: Q) -> Box<dyn Future<Item = Option<T>, Error = ()>>
    where
        Q: ToString,
        T: DeserializeOwned,
    {
        Box::new(futures::future::ok(Some(
            serde_json::from_str(&q.to_string()).unwrap(),
        )))
    }
}
error: the `json_query` method cannot be invoked on a trait object
  --> src/main.rs:27:23
   |
27 |         MyDbClient {}.json_query::<Weather, ToString>(serde_json::to_string(&weather).unwrap())
   |                       ^^^^^^^^^^
   |
   = note: another candidate was found in the following trait, perhaps add a `use` for it:
           `use crate::integrations::serde_integration::MyDbSerdeORM;`

The error is also unhelpful, as it's suggesting to add an already existing import.

How do I fix the error present in my MRE? How might this be implemented in a better, more rustic way?

Empty2k12
  • 475
  • 12
  • 34
  • As mentioned in your deleted previous question, why do you believe that a feature flag is required to reproduce the problem? Replace all your `feature = "serde-orm"` with the equivalent of `true` (a.k.a. remove them); do you get the same problem? If so, then the feature flag aspect is meaningless for the question. – Shepmaster Jun 04 '19 at 17:24
  • It looks like your question might be answered by the answers of [Why does a generic method inside a trait require trait object to be sized?](https://stackoverflow.com/q/42620022/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Jun 04 '19 at 17:26
  • Please review the [Rust-specific MCVE tips](//stackoverflow.com/tags/rust/info) to reduce your original code for posting here. For example, you can combine all of your files into one (like you did for `mod integrations`). – Shepmaster Jun 04 '19 at 17:27
  • 1
    See also [Is it possible to have a generic function on a trait?](https://stackoverflow.com/q/49952612/155423); – Shepmaster Jun 04 '19 at 17:29
  • @Shepmaster I have updated the question. As you can clearly tell, it's about the Rust compilers inability to detect that a method is implemented. Are you habe to help me in this case? https://stackoverflow.com/q/42620022/1390727 does not answer my question. If you would have taken the time to actually look at my question, you'd have seen that my error happens when I am calling a method which is implemented on a trait, not when using a trait inside a struct. – Empty2k12 Jun 04 '19 at 17:34
  • 2
    You could likely remove the return value from `json_query`, the `DeserializeOwned` bound; many other things. We really emphasize the **M**inimal part. [Example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6cb69b62ae33a0900d65b37f8c53a435) – Shepmaster Jun 04 '19 at 17:35
  • You are paying attention to the wrong part of the error message. It's unfortunate that it suggests that, but the important part is the `error` not the `note`: the `json_query` method cannot be invoked on a trait object. That's what all the linked questions talk about. – Shepmaster Jun 04 '19 at 17:37
  • Do you have a concrete example for a fix? I think this question warrants a separate answer, as it might be a similar to the questions linked, but the context of the error occurring is definitely different. I tried solving my specific error by searching for causes on Google first, which netted no, or insignificant answers which were unhelpful in actually solving the problem. – Empty2k12 Jun 04 '19 at 17:37
  • 1
    There *is* no fix; that's what the answers I'm linking to say. – Shepmaster Jun 04 '19 at 17:41
  • There's an answer. Thanks! The second part from my question specifically asks how to do this in a rustic way. For that part of the question, a more concrete Minimal Reproducible Example would have been good, as the specific use case of guarding Serde (or any other crate of matter of fact) as I am sure I am not the first and won't be the last to do something like that. I tried Googling "Serde behind a feature flag" which netted no meaningful results, so having a reference implementation here would be insanely useful. – Empty2k12 Jun 04 '19 at 17:46
  • 1
    [Further reduced](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e5e40326e1960bd10d88abd9857ffb79) — this was accomplished by just deleting things while the error message stayed consistent. – Shepmaster Jun 04 '19 at 17:46
  • 2
    @Shepmaster: In your reduced example, simple remove the `::` fixes everything – msrd0 Jun 04 '19 at 18:03
  • @msrd0 sure; but why did OP include that? Presumably they have a good reason. – Shepmaster Jun 04 '19 at 18:05
  • @Shepmaster I included it, since `Q` needed to be of type `ToString`, I never knew you could simply use `_`. – Empty2k12 Jun 04 '19 at 18:06
  • @Shepmaster the OP probably included it since he specified the `T` type and didn't know he could leave the `Q` type out (as `_`) and simple copied it from the `where` clause – msrd0 Jun 04 '19 at 18:07
  • @Empty2k12 but the two things are different and have different meanings. In that case, [you should have just passed `String`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c863d4a41c4b421eef6000be19e1622a) – Shepmaster Jun 04 '19 at 18:07
  • In this case correct. In the deleted question you referred to earlier, you have seen that I am not actually passing a `String`, but rather a `MyDbQuery`. This is what happens when you reduce code down so much, it looses the meaning it had before. – Empty2k12 Jun 04 '19 at 18:11
  • Possible duplicate of [Why does a generic method inside a trait require trait object to be sized?](https://stackoverflow.com/questions/42620022/why-does-a-generic-method-inside-a-trait-require-trait-object-to-be-sized) – Stargateur Jun 04 '19 at 20:06
  • @Stargateur well that only solves part of the issue. Also hard to find with that error message, since it never mentioned `Sized` anywhere – msrd0 Jun 04 '19 at 20:41
  • @msrd0 first ask two questions in one is bad. secondly the duplicate perfectly answer the real issue with high quality answer. third duplicate is about answer not question even if the error doesn't match, the answer could still be the same. four the second question is a little of topic on SO. – Stargateur Jun 04 '19 at 20:48
  • @Stargateur asking two questions in one is bad, but in this case the OP didn't/couldn't know that there were multiple issues in his/her code that prevented the code from compiling – msrd0 Jun 04 '19 at 20:59

1 Answers1

2

There is a simple fix to your problem: Since you have defined the MyDbClient struct in your own crate, you can simply implement the methods you want for it without specifying another trait. This will work with your original example:

use futures::Future;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

pub struct MyDbClient {
    pub url: String,
}

#[cfg(feature = "serde-orm")]
impl MyDbClient {
    pub fn json_query<T: 'static, Q>(self, q: Q) -> Box<dyn Future<Item = Option<T>, Error = ()>>
    where
        Q: ToString,
        T: DeserializeOwned,
    {
        Box::new(futures::future::ok(Some(
            serde_json::from_str(&q.to_string()).unwrap(),
        )))
    }
}

#[cfg_attr(feature = "serde-orm", derive(Serialize, Deserialize, Debug))]
pub struct Weather {
    temperature: i32,
}

#[cfg(test)]
#[cfg(feature = "serde-orm")]
mod tests {
    use super::*;

    #[test]
    fn mrp() {
        let weather = Weather { temperature: 82 };

        let client: MyDbClient = MyDbClient {
            url: "localhost".to_owned(),
        };

        client.json_query::<Weather, _>(serde_json::to_string(&weather).unwrap());
    }
}

This has one drawback though, since you can't reuse the implementation for a type other than MyDbClient. While that won't work for everyone, this can probably accommodate your use case.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
msrd0
  • 7,816
  • 9
  • 47
  • 82
  • Thank you for actually answering my question. This _was_ a pretty easy fix after all. Your method, which not being 100% the same with my original, works perfectly in my case. – Empty2k12 Jun 04 '19 at 18:04