Как я могу вернуть что-то осмысленное из универсальной функции, если нечего возвращать? - PullRequest
0 голосов
/ 06 февраля 2019

Я создаю библиотеку в Rust, в которой есть метод send, который выполняет HTTP-запросы к локальному RPC-серверу с использованием reqwest.

Этот метод возвращает универсальный тип R в Resultгде R: DeserializeOwned.После создания правильных типов для каждого ответа, serde_json::from_str() может получить мне тип.

Если по запросу нет ответа, как я могу заставить send по-прежнему возвращать что-то значимое?

Это код, который у меня сейчас есть:

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))

Теперь я вынужден создать и вернуть Err, но технически запрос, не возвращающий ответа, является ожидаемым поведением, поэтому я хочу вернуть что-то отличное от Err.

Я попытался обойти это, обернув R с Option, но это означает, что мне нужно дважды развернуть каждый ответ, и 98% ответов от reqwest действительно содержат данные в своем ответе, поэтомунемного похоже на излишество.

Я также пытался вернуть самодельный тип EmptyResponse, но компилятор жалуется: expected type R, found type EmptyResponse.Я думаю, что возвращение типа EmptyResponse было бы тем, что я хочу, но, возможно, кто-то может пролить несколько советов о том, как, возможно, сделать это еще лучше.

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

Прагматичный ответ состоит в том, чтобы иметь две функции:

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,

Если ваш сервер возвращает значение, которое может быть десериализовано в тип (), тогда вы можете избежать накладных расходов на две функции.Однако это не случай для JSON, одного из самых распространенных форматов:

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");
}

Эта паника:

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

В миресо специализацией вы можете использовать его и вспомогательную черту, чтобы вернуться к одной функции:

#![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");
}
0 голосов
/ 07 февраля 2019

Вы можете вернуть Result<Option<R>, ApiError>, как показано в документации , а затем сопоставить его следующим образом:

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
}

Я попытался обойти это, обернув R с Option, но это означает, что я должен дважды развернуть каждый ответ, и 98% ответов от reqwest действительно содержат данные в своем ответе, так что это похоже на избыточное убийство.

Развертывание Option - это очень дешевая операция, беспокоиться не о чем.

...