How to make function to accept only serde structs?

I implemented function that should accept only serializable/deserializable serde structs.
But it accepts arg of any type:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Payload {
    guid: u64,
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Payload1 {
    guid: u64,
    name: String,
}

fn test<'a, T: Serialize + Deserialize<'a>>(arg: T) {
    //
}

fn main() {
    let p = Payload {
        guid: 1,
        name: String::from("x"),
    };
    
    test(10);
}

The sandbox: Rust Playground

Could somebody explain how to fix this and make function accept only serde structs ?
I need to allow function to accept another structs with #[derive(Serialize, Deserialize)]

1 Like

To me it seems to work fine since i32 implements Serialize and Deserialize while passing a new struct will in fact fail Rust Playground

1 Like

To OP: If you don't care about lifetime, just use Serialize + DeserializeOwned.

Edit: I replied to wrong person but I don't know how to change it :frowning:

1 Like

There is no way to restrict generic types to only structs. It's not something you should care about, either. What is your actual requirement, what are you trying to achieve with this, and why is the Serialize + Deserialize bound not sufficient?

3 Likes

I want to implement smth like this:

#[async_trait]
pub trait QueryHandler {
    fn new(
        sender: WSSender<Compat<TcpStream>>,
        receiver: WSReceiver<Compat<TcpStream>>
    ) -> Box<Self> {
        Self {
            sender,
            receiver,
        }
    }

    async fn handle<T: DeserializeOwned>(&mut self, payload: Payload) -> String {
        let query = Query {
            event: EVENT,
            payload,
        };

        let request = serde_json::to_string(&query).unwrap();
        self.sender.send(request).await;

        let response = self.receiver.recv().await;

        // ...
    }
}

this is trait which I will apply on custom structs:

pub struct Handler {
    sender: Sender<Compat<TcpStream>>,
    receiver: Receiver<Compat<TcpStream>>,
}

#[async_trait]
impl QueryHandler for Handler {
    fn new(sender: WSSender<Compat<TcpStream>>, receiver: WSReceiver<Compat<TcpStream>>) -> Self {
        Self {
            sender,
            receiver,
        }
    }

    async fn handle(&mut self, payload: Payload) -> String {
        let query = Query {
            event: EVENT,
            payload,
        };

        serde_json::to_string(&query).unwrap()
    }
}

#[async_trait]
impl Handler {
    pub async fn get_player(&mut self) {
        self.handle(payload).await;
    }
}

Then use that trait as a bound instead of serde's.

1 Like

There isn't anything in that code that would indicate to me that it would be an error to pass it something that isn't a struct if it implements your own trait or DeserializeOwned. "You must pass a struct" l'art pour l'art is not a meaningful restriction; if you merely want something that is deserializeable, use the Deserialize bound, and let users pass anything deserializable.

2 Likes

well, I refactored my trait into this:

#[async_trait]
pub trait QueryHandler {
    type Response;

    fn event() -> String;

    async fn handle(
        sender: &mut WSSender<Compat<TcpStream>>,
        receiver: &mut WSReceiver<Compat<TcpStream>>,
        request: String
    ) -> Result<Self::Response, Error> {
        sender.send_text(request).await.unwrap();

        let mut response = Vec::new();
        receiver.receive_data(&mut response).await.unwrap();

        let response = String::from_utf8_lossy(&response).to_string();
        let result = serde_json::from_str(response.as_str()).unwrap();

        Ok(result)
    }
}

but for now I got an error on compile:

error[E0277]: the trait bound `<Self as QueryHandler>::Response: Deserialize<'_>` is not satisfied
    --> src\types\traits.rs:44:22
     |
44   |         let result = serde_json::from_str(response.as_str()).unwrap();
     |                      ^^^^^^^^^^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `<Self as QueryHandler>::Response`
     |
note: required by a bound in `serde_json::from_str`
    --> C:\Users\user\.cargo\registry\src\github.com-1ecc6299db9ec823\serde_json-1.0.86\src\de.rs:2611:8
     |
2611 |     T: de::Deserialize<'a>,
     |        ^^^^^^^^^^^^^^^^^^^ required by this bound in `serde_json::from_str`

it's not clear where should I implement Deserialize.

the trait bound `<Self as QueryHandler>::Response: Deserialize<'_>` is not satisfied

meaning you need to add a bound to Response.

type Response: serde::de::DeserializeOwned;

(I made the bound DeserializeOwned instead of Deserialize like suggested in the error message, this basically makes it so you don't have to worry about the lifetime in Deserialize<'_>. You can read more about it at Deserializer lifetimes · Serde)

By the way, there's no need to turn the response into a string before deserializing it, serde_json can deserialize from a byte array: from_slice in serde_json - Rust

2 Likes

works ! thank you very much !

thanks to all for hints and help !

You shouldn't. You are trying to use (i.e., depend on) it. So, you should indicate that all types substituted for that particular type parameter should implement Deserialize.

Rust generics are not like C++ templates. The body of a generic function is typechecked once and only once, against the constraints (i.e. trait and lifetime bounds) you explicitly put on the generic parameters. You are not allowed to rely on anything else about the types the generic type parameters stand for.

If you want to deserialize into a value of generic type, you have to put the Deserialize bound on the type parameter. If you want to clone it, you have to apply the Clone bound. If you want to compare it to values of the same type, you have to add the PartialEq bound, and so on.

The reason for this design is that it greatly improves reliability and code robustness. Library writers don't have to worry about their function not compiling when instantiated with unforeseen types. If a generic function typechecks once, it will work with any set of suitable substitutions.

3 Likes

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.