Rust结构体中有Box字段并实现异步trait

5
我遇到了一个问题,涉及到具有 Box 字段并且实现了 async 特性的结构体。具体来说:
error: future cannot be sent between threads safely

看起来错误是因为我在实现异步trait的结构体中使用了一个Box字段。

下面是我试图完成并遇到问题的最小示例。你可以在这里找到一个演示。

use async_trait::async_trait;

// My traits
#[async_trait]
pub trait InnerTrait {
    async fn inner_fn(&self) -> Result<(), ()>;
}

#[async_trait]
pub trait OuterTrait {
    async fn outer_fn(&self) -> Result<(), ()>;
}

// My structs
pub struct InnerStruct {}

impl InnerStruct {
    pub fn new() -> impl InnerTrait {
        InnerStruct {}
    }
}

pub struct OuterStruct {
    inner_trait: Box<dyn InnerTrait>,
}

impl OuterStruct {
    pub fn new(inner_trait: Box<dyn InnerTrait>) -> impl OuterTrait {
        OuterStruct { inner_trait }
    }
}

// My trait impls
#[async_trait]
impl InnerTrait for InnerStruct {
    async fn inner_fn(&self) -> Result<(), ()> {
        println!("InnerStruct.inner_fn");
        Ok(())
    }
}

#[async_trait]
impl OuterTrait for OuterStruct {
    async fn outer_fn(&self) -> Result<(), ()> {
        println!("OuterStruct.outer_fn");
        self.inner_trait.inner_fn().await;
        Ok(())
    }
}

#[tokio::main]
async fn main() {
    let inner_trait: Box<dyn InnerTrait> = Box::new(InnerStruct::new());
    let outter_trait: Box<dyn OuterTrait> = Box::new(OuterStruct::new(inner_trait));
    outter_trait.outer_fn().await;
}

首先,我该如何解决这个问题?

其次,我试图为结构体编写特质(trait),使得实现它的结构体可以轻松地与其他结构体交换,类似于我可能在Java中为对象编写接口。我意识到这可能不是我在Rust中设计组件的正确方式,但作为一个初学者,我不确定应该如何处理基于特质(trait)的设计。如果这不是惯用的Rust方法,你会如何重新设计它,以便仍然能够实现设计目标(在整个堆栈中创建和使用特质(trait),以允许轻松的impl替换)?

谢谢。


如果您不想让它们被发送,请使用:#[async_trait(?Send)] - Ibraheem Ahmed
2个回答

4

Async函数通过async_trait 转换为以下形式:

fn your_function<'async_trait>(
        &'async_trait self,
    ) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'async_trait>>
    where
        Self: Sync + 'async_trait,

请注意SendSync 的要求,这告诉编译器该future可以在线程之间传输和共享。如果您正在使用多线程执行程序(例如tokio),这非常有用。
如果您不需要线程安全的 futures,您可以通过使用 #[async_trait(?Send)] 来避免在异步特质方法上放置 SendSync 约束:
#[async_trait(?Send)]
pub trait InnerTrait {
    async fn inner_fn(&self) -> Result<(), ()>;
}

#[async_trait(?Send)]
impl InnerTrait for InnerStruct {
    async fn inner_fn(&self) -> Result<(), ()> {
        println!("InnerStruct.inner_fn");
        Ok(())
    }
}

另一方面,如果您需要线程安全的future,可以向您的结构体添加SendSync约束,这将满足async_trait的future要求:

pub struct OuterStruct {
    inner_trait: Box<dyn InnerTrait + Send + Sync>,
}

2

一些类型可以安全地在线程之间传送,而另一些类型则不行。在 Rust 中,这是通过类型是否实现了 Send 特性来表达的。还有另一个特性 Sync,它标记了一种类型可以通过引用在线程之间安全地共享。

为了表达你的特质对象必须是安全可发送的,你可以添加限制:

pub struct OuterStruct {
    inner_trait: Box<dyn InnerTrait + Send>,
}

然而,OuterStruct 包含对 InnerTrait 对象的引用,因此,如果将 OuterStruct 在线程之间传递,则同时共享内部的特质对象的引用。因此,为了使 OuterStruct 可以 Send,必须实现 InnerTraitSync

因此,需要这样做:

pub struct OuterStruct {
    inner_trait: Box<dyn InnerTrait + Sync + Send>,
}

您需要在使用特征对象类型的任何地方添加这些约束。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接