Использование асинхронных функций, которые заимствуют аргументы в контекстах, где необходим объект признака

#asynchronous #rust

#асинхронный #Ржавчина

Вопрос:

Недавно я начал играть с асинхронными потоками в Rust, и я продолжаю попадать в ситуации, когда я хочу использовать асинхронные функции в реализации a Stream . Асинхронные функции часто поступают из библиотек, которые я не контролирую, но для примера предположим, что они выглядят так:

 async fn bar_str(s: amp;str) -> String {
    s.to_string()
}

async fn bar_string(s: String) -> String {
    s
}
  

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

 use std::future::Future;

trait Foo {
    fn bar(self) -> Box<dyn Future<Output = String>>;
}
  

В данном String случае это работает так, как вы ожидаете:

 impl Foo for String {
    fn bar(self) -> Box<dyn Future<Output = String>> {
        Box::new(bar_string(self))
    }
}
  

В случае, когда асинхронная функция заимствует, это не так.

 impl Foo for amp;str {
    fn bar(self) -> Box<Future<Output = String>> {
        Box::new(bar_str(self))
    }
}
  

Не удается скомпилировать:

 error[E0495]: cannot infer an appropriate lifetime for lifetime parameter '_ in function call due to conflicting requirements
  --> foo.rs:23:18
   |
23 |         Box::new(bar_str(self))
   |                  ^^^^^^^^^^^^^
   |
...
  

Я могу понять, почему это проблема, и я понимаю, что async fn синтаксис обеспечивает специальную обработку для заимствованных аргументов, подобных этому (хотя я ничего не знаю о том, как это на самом деле проверяется, десугарируется и т. Д.).

Мой вопрос о том, что лучше всего делать в таких ситуациях в целом. Есть ли какой-то способ, которым я могу воспроизвести действие магии async fn в моем не- async fn коде? Должен ли я просто избегать заимствования в асинхронных функциях (когда я могу, поскольку это часто решение, которое я не принимал)? В коде, который я сейчас пишу, я с удовольствием использую экспериментальные или не обязательно перспективные решения, если они облегчают чтение и запись такого рода вещей.

Ответ №1:

Я думаю, что проблема не столько в async том, сколько в Box<dyn Trait> том. На самом деле это можно воспроизвести с помощью простого признака:

 use std::fmt::Debug;

trait Foo {
    fn foo(self) -> Box<dyn Debug>;
}

impl Foo for String {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self)
    }
}

impl Foo for amp;str {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self) // <--- Error here (line 15)
    }
}
  

Полное сообщение об ошибке:

 error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/lib.rs:15:18
   |
15 |         Box::new(self)
   |                  ^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the impl at 13:14...
  --> src/lib.rs:13:14
   |
13 | impl Foo for amp;str {
   |              ^
note: ...so that the expression is assignable
  --> src/lib.rs:15:18
   |
15 |         Box::new(self)
   |                  ^^^^
   = note: expected `amp;str`
              found `amp;str`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable
  --> src/lib.rs:15:9
   |
15 |         Box::new(self)
   |         ^^^^^^^^^^^^^^
   = note: expected `std::boxed::Box<(dyn std::fmt::Debug   'static)>`
              found `std::boxed::Box<dyn std::fmt::Debug>`
  

В последних двух строках есть хороший намек на то, что происходит… что это Box<(dyn Debug 'static)> за штука?

Когда вы пишете dyn Trait , на самом деле существует неявное 'static ограничение на тип, который реализует этот признак, так что это одно и то же:

 Box<dyn Debug>
Box<(dyn Debug   'static)>
  

Но это означает, что мы можем вставить только значение, тип которого 'static . И amp;'a str не является статическим типом, поэтому его нельзя поместить в коробку таким образом.

Простое решение, как обычно, заключается в клонировании, если это вообще возможно. Это компилируется, и это не слишком уродливо:

 impl Foo for amp;str {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self.to_owned())
    }
}
  

Или, если вы используете только статические строки, то amp;'static str на самом деле является статическим, и вы можете написать:

 impl Foo for amp;'static str {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self)
    }
}
  

Если вы действительно хотите или должны заимствовать, то упакованный объект dyn должен быть универсальным в течение некоторого времени жизни. Вы должны изменить возвращаемый тип вашего признака, что-то вроде этого:

 use std::fmt::Debug;

trait Foo<'a> {
    fn foo(self) -> Box<dyn Debug   'a>;
}

impl Foo<'static> for String {
    fn foo(self) -> Box<dyn Debug> {
        Box::new(self)
    }
}

impl<'a> Foo<'a> for amp;'a str {
    fn foo(self) -> Box<dyn Debug   'a> {
        Box::new(self)
    }
}
  

Но будьте осторожны, теперь это Box<dyn Debug 'a> не статический тип сам по себе, но сам тип имеет время жизни 'a .

Комментарии:

1. Спасибо, это полезно, но я боюсь, что я, возможно, переоценил, и это еще больше сводит к минимуму. Например, я не могу клонировать, поскольку мне нужно вызывать функции (которые я не контролирую), которые требуют ссылки. Я также попытался добавить явный параметр времени жизни и столкнулся с проблемами, но я подробнее рассмотрю, как это работает.

2. @TravisBrown: Да, async проблемы часто представляют собой несколько проблем, накладываемых одна на другую. В любом случае ваш исходный код с явным временем жизни работает . Может быть, если вы можете опубликовать ссылку на игровую площадку, которая показывает новые проблемы?

3. Только что подтвердил, что потоковая передача через явное время жизни работает (по крайней мере, в одном из мест, где я сталкивался с этим в реальном коде)! Должно быть, я просто слишком быстро отказался от этого. Спасибо за разъяснение.