我应该使用泛型还是 Box<dyn> 在结构字段下使用特征方法?

问题描述 投票:0回答:1

我怎样才能创建一种代码,我可以在其中调用

let saleor_app = SaleorApp::new(config);
let saleor_app.apl.get("10.1:3000/gql/")

SaleorApp 有一个 dyn Trait / Generic 字段,它允许我调用一个函数并让任何一个不同的 APL/处理程序执行底层工作?它将通过环境变量进行选择。我知道我需要这方面的特征,但我不确定 SaleorApp 是否应该

struct SaleorApp<A: APL> {
  pub apl: A
}

或者

struct SaleorApp {
  pub apl: Box<dyn APL>
}

特点:

pub trait APL: Send + Sized + Sync + Clone + std::fmt::Debug {
    fn get(&self, saleor_api_url: &str) -> impl Future<Output = Result<AuthData>> + Send;
    fn set(&self, auth_data: AuthData) -> impl Future<Output = Result<()>> + Send;
    fn delete(&self, saleor_api_url: &str) -> impl Future<Output = Result<()>> + Send;
}

无论如何,由于多种原因,我都无法让它工作。

至于拥有一个 Box,我做不到,因为特征

APL
不能被制作成对象,因为它返回
impl Futures
,并且异步特征可能是不允许的(显然它们在 1.75 中稳定下来,但它们没有)不为我工作)。将特征中所有函数的整个返回类型装箱是我发现的唯一解决方案,但是将所有这些都发生在堆上听起来不太生锈。

当尝试使用仿制药时,我失败了,因为:

pub fn create_app<A: APL>(config: Config) -> anyhow::Result<SaleorApp<A>> {
    use AplType::{Env, File, Redis};
    SaleorApp {
        apl: match config.apl {
            Env => EnvApl {}?,
            File => FileApl { path: "apls.txt" }?,
            Redis => RedisApl::new(config.apl_url, config.app_api_base_url)?,
        },
    }
}

给出错误:

`match` arms have incompatible types
expected `RedisApl`, found `EnvApl` [E0308]

expected A, found RedisApl

如何实现这个功能? 谢谢你的提示:)

generics rust traits rust-futures
1个回答
0
投票

如果您想使用泛型来决定使用哪个实现,那么您必须在编译时决定使用哪个实现,因为 Rust 将发出特定于您选择的实现的代码。由于您希望根据环境或配置选项来完成此工作,因此需要使用特征对象(即

dyn
Box
Arc
、引用或类似对象)。

现在,确实不能在特征对象中使用

impl
,因为编译器需要知道它返回的对象的类型(和大小)。如果您使用
async-trait
板条箱,它可以通过装箱返回值来有效地实现这一点,就像您提到的那样,只是使用一些更好的语法。您当然可以在 1.75 中使用新的异步特征功能,但因为我也尝试针对旧版本的 Rust,所以我选择使用
async-trait

确实,很多时候通过使用泛型而不是特征对象来静态地做出这些选择会更好、性能更高,并且可以更快地避免装箱代码。但是,就您而言,如果您想采用您所拥有的方法,那么实际上没有太多选择,并且基于配置在运行时选择实现是一个有效且合法的选择,所以我认为这没有什么大问题接近。

代码看起来有点像这样:

struct SaleorApp {
  pub apl: Box<dyn APL>
}

#[async_trait]
pub trait APL: Send + Sync + std::fmt::Debug {
    async fn get(&self, saleor_api_url: &str) -> Result<AuthData>;
    async fn set(&self, auth_data: AuthData) ->  Result<()>;
    async fn delete(&self, saleor_api_url: &str) -> Result<()>;
}

pub fn create_app(config: Config) -> anyhow::Result<SaleorApp> {
    use AplType::{Env, File, Redis};
    Ok(SaleorApp {
        apl: match config.apl {
            Env => Box::new(EnvApl {}),
            File => Box::new(FileApl { path: "apls.txt" }),
            Redis => Box::new(RedisApl{}),
        },
    })
}

请注意,

Clone
Sized
不能用于特征对象,因为它们需要
Sized
,并且特征对象在编译时没有已知的大小。因此,我在上面省略了它们的包含。

© www.soinside.com 2019 - 2024. All rights reserved.