rust 如果参数为“发送”,则返回“将来发送”

rta7y2nd  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(127)

我可以将函数参数的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)));
}

playground
现在,我想把这样的功能转移到一个特征上,这需要使用异步特性(这是一个代码生成器,它可以有效地将我的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)));
}

playground
但实际上,我并不希望它们是分开的,我希望它们是一个类似于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)));
}

在Rust中有这样的可能吗?我可以手动编写async-trait magic并修改它,而不是使用async-trait crate,如果这是一种使它工作的方式。
我有一些想法,但它们还没有真正开花结果:

  • 使用min-specialization来专门化Send?但是看起来这个特性在短期内不会稳定下来,所以可能不是最好的选择。
  • 返回一个自定义的MyFuture类型,而不仅仅是impl Futureimpl Send for MyFuture where T: Send?这可能很困难,因为我必须能够命名Future,而async代码通常会生成无法命名的impl Future类型。
  • 编写一个过程宏,如果它识别出输入类型是Send,就将+ Send添加到返回类型。实际上,过程宏能检测到某个类型是否实现了Send吗?我猜这是不可能的,因为它们只对令牌流起作用。
ctzwtxfj

ctzwtxfj1#

(2)是唯一可行的办法。
有两种方法可以使其工作:
1.手动书写未来,而不需要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 }
    }
}

Playground
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这只给我们留下了(2)。3我不推荐它,但它是可能的。
当traits中的异步fns稳定时,很可能会有一种机制(目前讨论的是有条件地实现它们,并在使用站点上使用边界来要求它们),但目前还没有这样的机制,即使在traits中的异步fns的夜间实现上也是如此。

相关问题