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

Я создаю библиотеку на 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 было бы то, что я хочу, но, возможно, кто-то может дать несколько советов о том, как сделать это еще лучше.


person n41r0j    schedule 06.02.2019    source источник
comment
Я думаю, что возврат типа EmptyResponse был бы тем, что я хочу — это невозможно: ошибка «Параметр ожидаемого типа» в конструктор универсальной структуры.   -  person Shepmaster    schedule 06.02.2019
comment
Я не уверен, что понимаю. Когда вы делаете запрос (вызов send), знаете ли вы заранее, будет ли ответ (в случае успеха) содержать данные или нет? Или вы делаете запрос, не зная, действительно ли будет возвращен R?   -  person trentcl    schedule 06.02.2019
comment
Я заранее знаю, какие типы будут возвращены, либо тип, который я хочу, либо тип Err. Даже для запроса, при успешном выполнении которого ничего не возвращается, в случае ошибки будет возвращен JSON с сообщением об ошибке.   -  person n41r0j    schedule 06.02.2019
comment
send<(), _> работает? Если вызывающая сторона знает, что ничего не ожидает, то она не должна указывать send на десериализацию во что-то   -  person trentcl    schedule 06.02.2019


Ответы (2)


Вы можете вернуть 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 — очень дешевая операция, не о чем беспокоиться.

person Laney    schedule 07.02.2019
comment
Этот if let без else действительно подходит только тогда, когда вы хотите просто игнорировать ошибки и продолжать, чего не должно быть в большинстве случаев. Я ожидаю, что .unwrap() (паника) или ? (распространение ошибки вверх по стеку вызовов) будут более полезными. - person trentcl; 07.02.2019
comment
Я не думаю, что OP беспокоит не производительность двойной развертки, а эргономика. Предлагаемое вами решение требует, чтобы каждый сайт вызова имел примерно 6 дополнительных строк шаблонного кода. В 98% ответов пустой ответ будет воспринят как ошибка, везде дублирующийся код. - person Shepmaster; 07.02.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");
}
person Shepmaster    schedule 07.02.2019