genericsrustleptos

Optional generic callbacks/functions parameters for structure in the Rust


Hello I am implementing a Rust application with Leptos. I am experimenting a lot, and learning at the same time, so I am doing things that probably are not the best, but I want to solve problems I see. The question is pure theoretical. I am not using the below client in my production. It's just interesting case for me.

I have the following implementation

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

pub trait ApiResourceQuery {
    fn path(&self) -> &str;
}

pub struct ApiRequestBuilder<'a, Q, H, H2> {
    query: Q,
    client: Option<&'a reqwest::Client>, // client is marked as optional only for this example
    token: Option<&'a str>,
    on_begin_func: Option<H>,
    on_finish_func: Option<H2>,
}

impl<'a, Q, H, H2> ApiRequestBuilder<'a, Q, H, H2>
where
    Q: ApiResourceQuery + Serialize,
    H: FnOnce(),
    H2: FnOnce(),
{
    pub fn new(query: Q) -> Self {
        ApiRequestBuilder {
            query,
            client: None,
            token: None,
            on_begin_func: None,
            on_finish_func: None,
        }
    }

    pub fn with_token(mut self, token: &'a str) -> Self {
        self.token = Some(token);

        self
    }

    pub fn with_client(mut self, client: &'a reqwest::Client) -> Self {
        self.client = Some(client);

        self
    }

    pub fn on_begin(mut self, func: H) -> Self {
        self.on_begin_func = Some(func);

        self
    }

    pub fn on_finish(mut self, func: H2) -> Self {
        self.on_finish_func = Some(func);

        self
    }
    pub /* async */ fn execute<R>(self, on_successful: impl FnOnce(R))
    where
        R: DeserializeOwned + Default, // default is temporary only for this example
    {
        if let Some(handler) = self.on_begin_func {
            handler();
        }

        let res: Result<R, ()> = Ok(R::default());
        on_successful(res.unwrap()); // unwrap only for the example purposes


        if let Some(handler) = self.on_finish_func {
            handler();
        }
    }
}

#[derive(Serialize, Deserialize)]
struct GetUserQuery {}

impl ApiResourceQuery for GetUserQuery {
    fn path(&self) -> &str {
        "/api/resource"
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct GetUserResponse {
    pub username: String
}

impl Default for GetUserResponse {
    fn default() -> Self {
        Self {
            username: "default username".into(),
        }
    }
}

And I can use it in the following way:


fn main() {
    // Usecase 1
    ApiRequestBuilder::new(GetUserQuery{})
      .on_begin(||println!("On begin handler1"))
      .on_finish(||println!("On finish handler1"))
      .execute(|user: GetUserResponse| println!("On successful1: {:?}", user));
      
      
    // Usecase 2
    // <'a, Q, H, H2>
    ApiRequestBuilder::<'_, _, _, fn()>::new(GetUserQuery{})
      .on_begin(||println!("On begin handler2"))
      .execute(|user: GetUserResponse| println!("On successful2: {:?}", user));
      
      
    // Usecase 3 - won't compile
    // <'a, Q, H, H2>
    ApiRequestBuilder::new(GetUserQuery{})
      .on_begin(||println!("On begin handler3"))
      .execute(|user: GetUserResponse| println!("On successful3: {:?}", user));
}

The problem is in use-case 3 it won't compile because of the following error:


error[E0284]: type annotations needed
   --> src/main.rs:111:5
    |
111 |     ApiRequestBuilder::new(GetUserQuery{})
    |     ^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `H2` declared on the struct `ApiRequestBuilder`
    |
    = note: cannot satisfy `<_ as FnOnce<()>>::Output == ()`
note: required by a bound in `ApiRequestBuilder::<'a, Q, H, H2>::new`
   --> src/main.rs:19:9
    |
19  |     H2: FnOnce(),
    |         ^^^^^^^^ required by this bound in `ApiRequestBuilder::<'a, Q, H, H2>::new`
20  | {
21  |     pub fn new(query: Q) -> Self {
    |            --- required by a bound in this associated function
help: consider specifying the generic arguments
    |
111 |     ApiRequestBuilder::<GetUserQuery, _, H2>(GetUserQuery{})
    |                      ~~~~~~~~~~~~~~~~~~~~~~~

Is there a simple way I can provide default values when I am want to skip one callback? Can they be defined during struct definition? Here example is simple but what if I have 2 more callback, let's say:

It is not very intuitive to annotation with 5 types when I want to call.


Solution

  • You can do it by specifying a default type for new():

    pub struct ApiRequestBuilder<'a, Q, H, H2> {
        query: Q,
        client: Option<&'a reqwest::Client>, // client is marked as optional only for this example
        token: Option<&'a str>,
        on_begin_func: Option<H>,
        on_finish_func: Option<H2>,
    }
    
    impl<'a, Q> ApiRequestBuilder<'a, Q, fn(), fn()>
    where
        Q: ApiResourceQuery + Serialize,
    {
        pub fn new(query: Q) -> Self {
            ApiRequestBuilder {
                query,
                client: None,
                token: None,
                on_begin_func: None,
                on_finish_func: None,
            }
        }
    }
    
    impl<'a, Q, H, H2> ApiRequestBuilder<'a, Q, H, H2>
    where
        Q: ApiResourceQuery + Serialize,
        H: FnOnce(),
        H2: FnOnce(),
    {
        pub fn with_token(mut self, token: &'a str) -> Self {
            self.token = Some(token);
    
            self
        }
    
        pub fn with_client(mut self, client: &'a reqwest::Client) -> Self {
            self.client = Some(client);
    
            self
        }
    
        pub fn on_begin<NewH: FnOnce()>(self, func: NewH) -> ApiRequestBuilder<'a, Q, NewH, H2> {
            ApiRequestBuilder {
                query: self.query,
                client: self.client,
                token: self.token,
                on_begin_func: Some(func),
                on_finish_func: self.on_finish_func,
            }
        }
    
        pub fn on_finish<NewH2: FnOnce()>(self, func: NewH2) -> ApiRequestBuilder<'a, Q, H, NewH2> {
            ApiRequestBuilder {
                query: self.query,
                client: self.client,
                token: self.token,
                on_begin_func: self.on_begin_func,
                on_finish_func: Some(func),
            }
        }
    
        pub fn execute<R>(self, on_successful: impl FnOnce(R))
        where
            R: DeserializeOwned + Default, // default is temporary only for this example
        {
            if let Some(handler) = self.on_begin_func {
                handler();
            }
    
            let res: Result<R, ()> = Ok(R::default());
            on_successful(res.unwrap()); // unwrap only for the example purposes
    
            if let Some(handler) = self.on_finish_func {
                handler();
            }
        }
    }