我是否可以将函数参数的Send特征传播到其返回类型,以便返回类型为impl Send当且仅当参数为?

Details:

异步函数有一个很好的功能.如果可以的话,它返回的Future自动是Send.在下面的示例中,如果函数的输入是Send,则该函数将创建一个等于SendFuture.

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-特征(这是一些代码生成器,它有效地将我的async fn写成返回Pin<Box<dyn Future>>的函数)或手动执行类似的操作.有没有办法以一种方式来保留这种自动发送行为,即如果TSend,则返回的FutureSend?下面的示例将其实现为两个单独的函数:

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 中可能发生吗?我可以手动编写异步特征魔法并修改它,而不是使用异步特征 crate ,如果这是一种让它工作的方法.

我有一些 idea ,但还没有真正落地:

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

推荐答案

(2)这是唯一可行的方法.

有两种方法可以让它发挥作用:

  1. 手动书写future ,不需要async.await的帮助.但这意味着手动书写future :
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>>>,并有条件地在future 实施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 }))
    }
}

Playground.

(1)不能处理特征,因 for each Imp都会有不同的future .这就只剩下(2)了.我不会推荐它,但这是可能的.

很有可能,当特征中的异步FNS稳定时,会有一种机制来实现(目前谈论的是有条件地实施它们,并使用使用站点的限制来要求它们),但目前还没有这样的事情,甚至在特征中的异步FNS的夜间实现上也是如此.

Rust相关问答推荐

什么样的 struct 可以避免使用RefCell?

创建包含缺失值的框架

Box::new()会从一个堆栈复制到另一个堆吗?

在Rust中有没有办法在没有UB的情况下在指针和U64之间进行转换?

如何指定不同的类型来常量Rust中的泛型参数?

重写Rust中的方法以使用`&;mut self`而不是`mut self`

Rust&;Tokio:如何处理更多的信号,而不仅仅是SIGINT,即SIGQUE?

关于 map 闭合求和的问题

循环访问枚举中的不同集合

需要哪些编译器优化来优化此递归调用?

返回优化后的标题:返回异步块的闭包的类型擦除

(let b = MyBox(5 as *const u8); &b; ) 和 (let b = &MyBox(5 as *const u8); ) 之间有什么区别

仅在运行测试时生成调试输出

Rust 将特性传递给依赖项

当用作函数参数时,不强制执行与绑定的关联类型

为什么我可以在没有生命周期问题的情况下内联调用 iter 和 collect?

如果返回类型是通用的,我可以返回 &str 输入的一部分吗?

为什么我返回的 impl Trait 的生命周期限制在其输入的生命周期内?

有没有比多个 push_str() 调用更好的方法将字符串链接在一起?

函数参数的 Rust 功能标志