#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. Только что подтвердил, что потоковая передача через явное время жизни работает (по крайней мере, в одном из мест, где я сталкивался с этим в реальном коде)! Должно быть, я просто слишком быстро отказался от этого. Спасибо за разъяснение.