Есть ли способ передать ссылку на обобщенную функцию и вернуть черту impl, которая не связана с временем жизни аргумента? - PullRequest
3 голосов
/ 10 июля 2019

Я обработал реальный пример в веб-приложении , который я решил с помощью ненужного выделения кучи, в следующем примере:

// Try replacing with (_: &String)
fn make_debug<T>(_: T) -> impl std::fmt::Debug {
    42u8
}

fn test() -> impl std::fmt::Debug {
    let value = "value".to_string();

    // try removing the ampersand to get this to compile
    make_debug(&value)
}

pub fn main() {
    println!("{:?}", test());
}

Как естькомпиляция этого кода дает мне:

error[E0597]: `value` does not live long enough
  --> src/main.rs:9:16
   |
5  | fn test() -> impl std::fmt::Debug {
   |              -------------------- opaque type requires that `value` is borrowed for `'static`
...
9  |     make_debug(&value)
   |                ^^^^^^ borrowed value does not live long enough
10 | }
   | - `value` dropped here while still borrowed

Я могу исправить эту ошибку по крайней мере двумя способами:

  1. Вместо передачи ссылки на value в test()передайте value сам
  2. Вместо параметра T явно укажите тип аргумента для make_debug как &String или &str

MyПонимание того, что происходит, заключается в том, что при наличии параметра средство проверки заимствования предполагает, что любое время жизни этого параметра влияет на выходное значение impl Debug.

Есть ли способ сохранить параметризованный код, продолжить передачув справке, и заставить заемщика принять его?

1 Ответ

1 голос
/ 10 июля 2019

Я думаю, что это связано с правилами того, как impl trait непрозрачные типы фиксируют время жизни.

Если в аргументе T есть времена жизни, то impl trait должен включать их.Дополнительные сроки жизни в сигнатуре типа следуют обычным правилам.

Для получения дополнительной информации см .:

Более полный ответ

Исходная цель: функция send_form принимаетвходной параметр типа & T, который отображается в двоичном представлении.Это двоичное представление принадлежит конечному значению Future, и от оригинала & T не осталось никакого остатка.Следовательно, срок службы & T не должен переживать существующую черту.Все хорошо.

Проблема возникает, когда сам T, кроме того, содержит ссылки с временами жизни.Если бы мы не использовали impl Trait, наша подпись выглядела бы примерно так:

fn send_form<T>(self, data: &T) -> SendFormFuture;

И, взглянув на SendFormFuture, мы можем легко заметить, что там вообще нет остатка T,Следовательно, даже если у T есть собственные времена жизни, с которыми мы имеем дело, мы знаем, что все ссылки используются в теле send_form и никогда не используются снова после SendFormFuture.

Однако с impl Future в качестве вывода мы не получаем таких гарантий.Нет никакого способа узнать, действительно ли конкретная реализация Future относится к T.

В случае, когда T не имеет ссылок, это все еще не является проблемой.Либо impl Future ссылается на T и полностью вступает во владение им, либо он не ссылается на него, и никаких проблем со временем жизни не возникает.

Однако, если T имеет ссылки, вы могли быв конечном итоге в ситуации, когда бетон impl Future удерживает ссылку, хранящуюся в T.Даже если impl Future владеет самим T, он не владеет значениями, на которые ссылается T.

Вот почему проверка заимствования должна быть консервативной и настаивать на том, чтобылюбые ссылки внутри T должны иметь 'static время жизни.

Единственный обходной путь, который я вижу, это обойти impl Future и быть явным в типе возвращаемого значения.Затем вы можете легко продемонстрировать контролеру заимствований, что тип вывода вообще не ссылается на тип ввода T, и любые ссылки в нем не имеют значения.

Исходный код в веб-клиенте actix дляsend_form выглядит следующим образом:

https://docs.rs/awc/0.2.1/src/awc/request.rs.html#503-522

pub fn send_form<T: Serialize>(
        self,
        value: &T,
    ) -> impl Future<
        Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
        Error = SendRequestError,
    > {
        let body = match serde_urlencoded::to_string(value) {
            Ok(body) => body,
            Err(e) => return Either::A(err(Error::from(e).into())),
        };

        // set content-type
        let slf = self.set_header_if_none(
            header::CONTENT_TYPE,
            "application/x-www-form-urlencoded",
        );

        Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
    }

Возможно, вам придется исправить библиотеку или написать собственную функцию, которая выполняет то же самое, но с конкретным типом.Если кто-нибудь еще знает, как справиться с этим очевидным ограничением impl trait, я бы с удовольствием его услышал.

Вот как далеко я продвинулся при переписывании send_form в awc (actix-веб-клиентская библиотека):

    pub fn send_form_alt<T: Serialize>(
        self,
        value: &T,
        // ) -> impl Future<
        //     Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
        //     Error = SendRequestError,
    ) -> Either<
        FutureResult<String, actix_http::error::Error>,
        impl Future<
            Item = crate::response::ClientResponse<impl futures::stream::Stream>,
            Error = SendRequestError,
        >,
    > {

Некоторые оговорки:

  • Either::B обязательно непрозрачный impl trait из Future.
  • Первый параметр FutureResult может быть на самом деле Void или как там называется эквивалент Void в Rust.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...