简体   繁体   中英

How can I return something meaningful from a generic function if there is nothing to return?

I'm building a library in Rust that has a send method that performs HTTP requests against a local RPC server using reqwest.

This method returns a generic type R in a Result where R: DeserializeOwned . After making the correct types for every response, serde_json::from_str() can get me the type.

If there is no response upon a request, how can I make send still return something meaningful?

This is the code I have now:

fn send<R, T>(
    &self,
    request: &RpcRequest<T>,
) -> Result<R, ApiError>
    where
        T: Serialize + Debug,
        R: DeserializeOwned + Debug,
let res = serde_json::from_str(&buf).map_err(|err| ClientError::Json(err))

I am now forced to create and return an Err , but technically, the request returning no response is expected behavior, so I want to return something other than an Err .

I tried to work around this by wrapping R with Option , but that means I have to double unwrap every response, and 98% of the responses from reqwest do have data in their response, so it feels a bit like overkill.

I also tried to return a self-made EmptyResponse type, but the compiler complains: expected type R, found type EmptyResponse . I think returning a type EmptyResponse would be what I want, but maybe someone can shed some tips on how to maybe do this even better.

You can return an Result<Option<R>, ApiError> as shown in the documentation , then match it like this:

match sender.send(request) {
    Ok(Some(r)) => {
        // process response
    }
    Ok(None) => {
        // process empty response
    }
    Err(e) => {
        // process error
    }
}
// or
if let Ok(Some(r)) = sender.send(request) {
    // process response
}

I tried to work around this by wrapping R with Option , but that means I have to double unwrap every response, and 98% of the responses from reqwest do have data in their response, so it feels a bit like overkill.

Unwrapping the Option is a very cheap operation, there's nothing to be worried about.

The pragmatic answer is to have two functions:

fn send<R, T>(&self, request: &RpcRequest<T>) -> Result<R, ApiError>
where
    T: Serialize + Debug,
    R: DeserializeOwned + Debug,
fn send_no_response<T>(&self, request: &RpcRequest<T>) -> Result<(), ApiError>
where
    T: Serialize + Debug,

If your server happens to return a value that can be deserialized into the type () , then you can avoid the overhead of two functions. However, this is not the case for JSON, one of the most common formats:

use serde::de::DeserializeOwned; // 1.0.85
use serde_json; // 1.0.37

type Error = Box<std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;

fn send<R>() -> Result<R, Error>
where
    R: DeserializeOwned,
{
    serde_json::from_str("").map_err(Into::into)
}

fn main() {
    let _r: () = send().expect("Unable to deserialize");
}

This panics:

Unable to deserialize: Error("EOF while parsing a value", line: 1, column: 0)

In a world with specialization, you can use it and a helper trait to reduce back to one function:

#![feature(specialization)]

use serde::de::DeserializeOwned; // 1.0.85
use serde_json; // 1.0.37

type Error = Box<std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;

type ApiResponse = &'static str;

trait FromApi: Sized {
    fn convert(response: ApiResponse) -> Result<Self, Error>;
}

impl<R> FromApi for R
where
    R: DeserializeOwned,
{
    default fn convert(response: ApiResponse) -> Result<R, Error> {
        eprintln!("deserializing the response");
        serde_json::from_str(response).map_err(Into::into)
    }
}

impl FromApi for () {
    fn convert(_response: ApiResponse) -> Result<Self, Error> {
        eprintln!("Ignoring the response");
        Ok(())
    }
}

fn send<R: FromApi>() -> Result<R> {
    eprintln!(r#""sending" the request"#);
    let api_response = "";
    R::convert(api_response)
}

fn main() {
    let _r: () = send().expect("Unable to deserialize");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM