Могу ли я создать свои собственные атрибуты условной компиляции?

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

Существуют атрибуты условной компиляции, такие как target_os, в которых хранятся такие значения, как linux или windows. Как я могу создать такой атрибут, например prefer_method, чтобы я и пользователь могли использовать его как в следующих фрагментах кода?

Мой ящик:

#[cfg(not(any(
    not(prefer_method),
    prefer_method = "fast",
    prefer_method = "small"
)))]
compile_error("invalid `prefer_method` value");

pub fn bla() {
    #[cfg(prefer_method = "fast")]
    foo_fast();

    #[cfg(prefer_method = "small")]
    foo_small();

    #[cfg(not(prefer_method))]
    foo_default();
}

pub fn foo_fast() {
    // Fast execution.
}

pub fn foo_small() {
    // Small binary file.
}

pub fn foo_default() {
    // Medium size, medium fast.
}

Ящик пользователя:

#[prefer_method = "small"]
extern crate my_crate;

fn f() {
    // Uses the `foo_small` function, the other `foo_*` functions will not end up in the binary.
    my_crate::bla();

    // But the user can also call any function, which of course will also end up in the binary.
    my_crate::foo_default();
}

Я знаю, что есть --cfg атрибутов, но AFAIK они представляют только логические флаги, а не значения перечисления, которые позволяют устанавливать несколько флагов, когда допустимо только одно значение перечисления.


person Maurice Kayser    schedule 18.02.2020    source источник


Ответы (1)


Во-первых, флаг --cfg поддерживает пары ключ-значение с использованием синтаксиса --cfg 'prefer_method="fast"'. Это позволит вам написать такой код:

#[cfg(prefer_method = "fast")]
fn foo_fast() { }

Вы также можете установить эти параметры cfg из сценария сборки . Например:

// build.rs
fn main() {
    println!("cargo:rustc-cfg=prefer_method=\"method_a\"");
}
// src/main.rs
#[cfg(prefer_method = "method_a")]
fn main() {
    println!("It's A");
}

#[cfg(prefer_method = "method_b")]
fn main() {
    println!("It's B");
}

#[cfg(not(any(prefer_method = "method_a", prefer_method = "method_b")))]
fn main() {
    println!("No preferred method");
}

Приведенный выше код приведет к созданию исполняемого файла, который печатает «It's A».

Нет синтаксиса, подобного тому, который вы предлагаете указать в настройках cfg. Лучше всего предоставить эти параметры пользователям ваших ящиков через Функции Cargo.

Например:

# Library Cargo.toml
# ...
[features]
method_a = []
method_b = []
// build.rs
fn main() {
    // prefer method A if both method A and B are selected
    if cfg!(feature = "method_a") {
        println!("cargo:rustc-cfg=prefer_method=\"method_a\"");
    } else if cfg!(feature = "method_b") {
        println!("cargo:rustc-cfg=prefer_method=\"method_b\"");
    }
}
# User Cargo.toml
# ...
[dependencies.my_crate]
version = "..."
features = ["method_a"]

Однако в этом случае я бы рекомендовал просто использовать функции Cargo непосредственно в вашем коде (т.е. #[cfg(feature = "fast")]), а не добавлять скрипт сборки, поскольку существует взаимно однозначное соответствие между функцией Cargo и добавляемым rustc-cfg.

person Andrew Gaspar    schedule 18.02.2020
comment
Спасибо! В дополнение к сценарию сборки вы можете добавить к своему ответу, что добавление следующего в .cargo/config также работает: toml [target.YOUR_TARGET] rustflags = ['--cfg', 'prefer_method="method_a"'] - person Maurice Kayser; 19.02.2020