如果参数为Send,则返回Future Send



是否可以将函数参数的Send特性传播到其返回类型,从而使返回类型为impl Send当且仅当参数为?

详细信息:

异步函数有一个很好的特性。如果可以的话,它返回的Future自动为Send。在下面的例子中,如果函数的输入为Send,异步函数将创建一个Future,即Send

struct MyStruct;
impl MyStruct {
// This async fn returns an `impl Future<Output=T> + Send` if `T` is Send.
// Otherwise, it returns an `impl Future<Output=T>` without `Send`.
async fn func<T>(&self, t: T) -> T {
t
}
}
fn assert_is_send(_v: impl Send) {}
fn main() {
// This works
assert_is_send(MyStruct.func(4u64));
// And the following correctly fails
assert_is_send(MyStruct.func(std::rc::Rc::new(4u64)));
}

操场

现在,我想将这样一个函数移动到一个trait中,这需要使用async trait(这是一个代码生成程序,它有效地将我的async fn写为返回Pin<Box<dyn Future>>的函数(或手动执行类似的操作。如果TSend,那么返回的Future被设为Send,有没有一种方法可以写这篇文章来保留这种自动发送行为?以下示例将其实现为两个独立的功能:

use std::pin::Pin;
use std::future::Future;
struct MyStruct;
impl MyStruct {
fn func_send<T: 'static + Send>(&self, t: T) -> Pin<Box<dyn Future<Output = T> + Send>> {
Box::pin(async{t})
}

fn func_not_send<T: 'static>(&self, t: T) -> Pin<Box<dyn Future<Output = T>>> {
Box::pin(async{t})
}
}
fn assert_is_send(_v: impl Send) {}
fn main() {
// This works
assert_is_send(MyStruct.func_send(4u64));
// And the following correctly fails
// assert_is_send(MyStruct.func(std::rc::Rc::new(4u64)));
}

操场

但实际上,我不希望他们分开。我希望它们是一个类似于async fn自动执行的功能。类似的东西

use std::pin::Pin;
use std::future::Future;
struct MyStruct;
impl MyStruct {
fn func<T: 'static + ?Send>(&self, t: T) -> Pin<Box<dyn Future<Output = T> + ?Send>> {
Box::pin(async{t})
}
}
fn assert_is_send(_v: impl Send) {}
fn main() {
// This should
assert_is_send(MyStruct.func(4u64));
// And this should fail
assert_is_send(MyStruct.func(std::rc::Rc::new(4u64)));
}

《铁锈》里可能有这样的东西吗?我可以手动编写异步特征魔术,并对其进行修改,而不是使用异步特征板条箱,如果这是一种使其工作的方法的话。

我有一些想法,但它们还没有真正取得成果:

  • 使用最小专业化来专门化Send?但这个功能似乎不会很快稳定下来,所以可能不是最好的选择
  • 返回一个自定义的MyFuture类型,而不是仅返回impl Futureimpl Send for MyFuture where T: Send?这可能很困难,因为我必须能够命名Futureasync代码通常会生成无法命名的impl Future类型
  • 如果识别出输入类型为Send,则编写一个将+ Send添加到返回类型的过程宏。实际上,程序宏能检测出某个类型是否实现了Send吗?我的猜测是这是不可能的,因为他们只是处理令牌流

(2(是唯一可行的方法。

有两种方法可以使其发挥作用:

  1. 手动编写future,无需async.await的帮助。但这意味着要手动书写未来:
enum ConditionalSendFut<T> {
Start { t: T },
Done,
}
impl<T> Unpin for ConditionalSendFut<T> {}
impl<T> Future for ConditionalSendFut<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<Self::Output> {
match &mut *self {
Self::Start { .. } => {
let t = match std::mem::replace(&mut *self, Self::Done) {
Self::Start { t } => t,
_ => unreachable!(),
};
Poll::Ready(t)
}
Self::Done => Poll::Pending,
}
}
}
struct MyStruct;
impl MyStruct {
fn func<T: 'static>(&self, t: T) -> ConditionalSendFut<T> {
ConditionalSendFut::Start { t }
}
}

游乐场。

  1. 存储一个Pin<Box<dyn Future<Output = T>>>,并在将来有条件地执行Send。但这需要unsafe代码,并手动确保您不会在.await点上持有其他非Send类型:
struct ConditionalSendFut<T>(Pin<Box<dyn Future<Output = T>>>);
// SAFETY: The only non-`Send` type we're holding across an `.await`
// point is `T`.
unsafe impl<T: Send> Send for ConditionalSendFut<T> {}
impl<T> Future for ConditionalSendFut<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
self.0.as_mut().poll(context)
}
}
struct MyStruct;
impl MyStruct {
fn func<T: 'static>(&self, t: T) -> ConditionalSendFut<T> {
ConditionalSendFut(Box::pin(async { t }))
}
}

游乐场。

(1( 无法处理特征,因为每个impl都有不同的未来。这只剩下(2(。我不建议这样做,但这是可能的。

当traits中的异步fns稳定时,很可能会有一种机制(目前谈论的是有条件地执行它们,并在使用站点上使用边界来要求它们(,但目前还没有这样的机制,即使是traits中异步fns的夜间实现也是如此。

最新更新