使用带有 impl 特征引用作为参数的 async_recursion 宏

Using async_recursion macro with an impl trait reference as argument

我正在尝试在将 impl 特征作为参数的构造函数上使用 #[async_recursion] 宏。 impl trait 只是围绕 reqwest 的垫片,所以我可以插入一个 mock 进行测试:

#[async_trait]
pub trait NetFuncs {
    async fn get(&self, url: &str) -> Result<String, Error>;
}

在我使我的构造函数递归之前它工作正常:

#[derive(Debug)]
pub struct Foo {
    map: serde_yaml::mapping::Mapping,
    filename: String,
    parent: Option<Box<Foo>>,
    receipt: Option<Receipt>,
}

impl Foo {
    #[async_recursion]
    pub async fn from_str(s: &str, filename: &str, net: &impl NetFuncs) -> Result<Foo, Error> {

抛出错误:


error: future cannot be sent between threads safely
   --> src/main.rs:97:5
    |
97  |     #[async_recursion]
    |     ^^^^^^^^^^^^^^^^^^ future created by async block is not `Send`
    |
note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`
   --> src/main.rs:125:17
    |
125 |                 net,
    |                 ^^^ has type `&impl NetFuncs` which is not `Send`, because `impl NetFuncs` is not `Sync`
    = note: required for the cast to the object type `dyn Future<Output = Result<Foo, Error>> + Send`
    = note: this error originates in the attribute macro `async_recursion` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider further restricting this bound
    |
98  |     pub async fn from_str(s: &str, filename: &str, net: &impl NetFuncs + std::marker::Sync) -> Result<Foo, Error> {
    |                                                                        +++++++++++++++++++

还有其他方法可以像我那样模拟网络进行测试,但我喜欢我的解决方案,至少在遇到此错误之前是这样。如何在不删除 net: &impl NetFuncs 参数的情况下修复此错误?

MRE

[package]
name = "mre2"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-recursion = "1.0"
async-trait = "0.1"
use async_trait::async_trait;
use async_recursion::async_recursion;

#[derive(Debug)]
pub struct Foo {
    s: String,
    filename: String,
    foo: String,
    parent: Option<Box<Foo>>,
}

#[async_trait]
pub trait NetFuncs {
    async fn get(&self, url: &str) -> String;
}

#[derive(Debug)]
pub struct FakeNet {}

#[async_trait]
impl NetFuncs for FakeNet {
    async fn get(&self, url: &str) -> String {
        "".to_string()
    }
}

impl Foo {
    #[async_recursion]
    pub async fn from_str(s: &str, filename: &str, net: &impl NetFuncs) -> Foo {
        Foo { s: s.to_string(), filename: filename.to_string(), parent: None, foo: net.get("").await.to_string() }
    }
}

问题是,正如编译器所解释的那样,&impl NetFuncs 可能不一定会实现 Send 但默认情况下 async_recursion 宏需要它,因此,您有两个选择:

  • 要求impl NetFuncsSync,因此&impl NetFuncsSend。这可以通过 &(impl NetFuncs + Sync) 或要求每个实施者实施 Send 来完成:trait NetFuncs: Sync.
  • 不要求结果未来是 SendAs documented in the async_recursion documentation,这可以通过将 #[async_recursion] 更改为 #[async_recursion(?Send)] 来完成。

没有宏它就可以工作,因为编译器生成的未来 Send 取决于 .await 点上的所有类型是否都是 Send:如果是,未来也是Send。如果不是,那也不是。宏将 async fn 更改为 fn ...() -> Pin<Box<dyn Future>>,不幸的是,不可能具有与 async fn 相同的行为 - 这只有编译器才能实现。因此,该宏允许您选择是否希望结果未来成为 Send - 意味着所有类型都应该是,还是不是。