Можете ли вы поделиться `cfg!` Деревом зависимостей с помощью Cargo? - PullRequest
0 голосов
/ 21 мая 2018

У меня есть библиотека 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 голосов
/ 21 мая 2018

У вас может быть функция, которая возвращает список доступных бэкэндов.

Затем, когда приложение хочет использовать вашу библиотеку, оно может вызвать эту функцию из своего build.rs, выбрать один из доступных бэкэндов.и передать его в качестве опции компилятору.

0 голосов
/ 21 мая 2018

У вас нет доступа к конфигурации библиотеки извне.

Вы никогда не сможете узнать конкретные типы бэкэндов из потребительского кода, поэтому вам придется придумать какой-то механизм, чтобы иметь возможностьчтобы создать их, принимая во внимание различные потребности каждого из их конструкторов.

Основная идея здесь состоит в том, чтобы ввести контекст, что-то вроде контекста внедрения зависимости, который вы можете использовать в объектно-ориентированном языке.Контекст содержит значения, которые могут понадобиться конструктору.

Для создания объектов признаков вам нужна черта:

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()))
    }
}
...