你能用货物分享`cfg !`依赖树吗?

时间:2018-05-21 11:17:22

标签: configuration rust rust-cargo

我有一个Rust库,它有一些平台抽象形式的"后端"。该库使用build.rs进行一些平台检查,并根据可以构建的后端设置一些编译时配置变量。然后在代码中,后端代码被保护如下:

#[cfg(backend1)]
struct Backend1 { ... }
#[cfg(backend2)]
struct Backend2 { ... }
...

此库的使用者想要实例化适用于当前平台的后端。理想情况下,您可以执行以下操作:

fn get_backend() -> Box<Backend> {
    #[cfg(backend1)]
    return mylib::backends::Backend1::new(...);
    #[cfg(backend2)]
    return mylib::backends::Backend2::new(...);
    ...
}

但是,mylib中的配置变量不会分享给消费者,因此#[cfg(backend1)]无法按预期工作。

有没有办法实现所需的行为,而无需为构建图书馆消费者的人员进行人工干预?我不希望用户必须手动传递可用后端列表。这似乎应该是可自动化的。

请注意,mylib内置的后端结构完全不存在,这意味着消费者无法引用它们。消费者需要使用条件编译来确保仅引用内置于mylib的后端。

任何给定平台可能有多个后端,在这种情况下,消费者应该能够选择哪一个。

2 个答案:

答案 0 :(得分:1)

您无法从外部访问图书馆的配置。

您永远无法从消费者代码中了解后端的具体类型,因此您必须提出一些能够构建它们的机制,同时考虑到每个构造函数的不同需求。

这里的基本思想是引入一个上下文,类似于您可能在面向对象语言中使用的依赖注入上下文。上下文包含构造函数可能需要的值。

要创建特征对象,您需要一个特征:

pub trait Backend {
    // all the common stuff for backends
}

用于构造后端的特性,以及用于保存这些后端所需的所有可能配置变量的结构。这可能与Backend具有相同的特性,因为new方法会阻止它成为对象。大多数变量都是可选的,因为并非所有后端都需要它们:

pub trait BackendContstruct {
    fn new(ctx: &BackendContext) -> Result<Box<Backend>, BackendError>;
}

pub struct BackendContext<'a> {
    var_1: Option<&'a str>,
    var_2: Option<&'a str>,
    another: Option<bool>,
    // etc
}

如果提供错误的变量,则需要返回错误。不幸的是,构造动态意味着错误是运行时而不是编译时:

pub struct BackendError(String);

每个后端的可用性取决于平台支持。因此,使他们的定义依赖于平台:

#[cfg(platform1)]
mod backend1 {
    pub struct Backend1;
    impl ::Backend for Backend1 {}
    impl ::BackendContstruct for Backend1 {
        fn new(ctx: &::BackendContext) -> Result<Box<::Backend>, ::BackendError> {
            if ctx.var_1.is_none() {
                Err(::BackendError("Backend1 requires val_1 to initialize".to_string()))
            } else {
                Ok(Box::new(Backend1 {}))
            }
        }
    }
}

#[cfg(platform1)]
#[cfg(platform2)]
mod backend2 {
    pub struct Backend2;
    impl ::Backend for Backend2 {}
    impl ::BackendContstruct for Backend2 {
        fn new(ctx: &::BackendContext) -> Result<Box<::Backend>, ::BackendError> {
            Ok(Box::new(Backend2 {}))
        }
    }
}

没有具体类型是公开的,任何可能都不存在。因此,提供一个枚举,以便消费者可以指定他们想要的后端:

pub enum BackendType {
    // these names are available in all configurations
    Default, Backend1, Backend2, Backend3
}

构建后端的功能。请求不受支持的后端或错过上下文中的必需变量将是Err。应鼓励消费者使用Default变体,该变体应在任何平台上具有有效的后端:

pub fn create_backend(backend: BackendType, ctx: &BackendContext) -> Result<Box<Backend>, BackendError> {
    match backend {
        #[cfg(platform1)]
        #[cfg(platform2)]
        BackendType::Default => Backend2::new(ctx),
        #[cfg(platform1)]
        BackendType::Backend1 => Backend1::new(ctx),
        #[cfg(platform1)]
        #[cfg(platform2)]
        BackendType::Backend2 => Backend2::new(ctx),
        _ => Err(BackendError("Backend not available".to_string()))
    }
}

答案 1 :(得分:0)

您可以使用一个返回可用后端列表的函数。

然后,当应用程序想要使用您的库时,它可以从其build.rs调用该函数,选择一个可用的后端,并将其作为选项传递给编译器。