I had an idea in the middle of the "Testing and mocking based on name conversion" thread that I think should maybe be discussed independently.
If you want to conditionally compile one item under some cfg
condition, and a different item under the opposite of that cfg
condition, right now you're forced to repeat the condition:
#[cfg_attr(test, path = "database_mock.rs")]
#[cfg_attr(not(test), path = "database_real.rs")]
mod database;
... and that's probably the nicest case. It can get much worse:
#[cfg(target_os = "macos")]
pub(crate) fn get_screen_resolution() {
// several dozen lines of code
}
#[cfg(target_os = "linux")]
pub(crate) fn get_screen_resolution() {
// several dozen lines of code
}
#[cfg(target_os = "windows")]
pub(crate) fn get_screen_resolution() {
// several dozen lines of code
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
pub(crate) fn get_screen_resolution() {
unimplemented!();
}
I think this points at a genuine missing language feature: compile-time cfg-based if-then-else chains.
cfg_attr
can easily be extended to support if-then-else, we just need some notation, perhaps
#[cfg_attr(
test => path = "database_mock.rs";
target_os = "android" => path = "database_android.rs";
/* otherwise */ path = "database_default.rs";
)]
mod database;
(semicolons separate the arms so you can still write multiple comma-separated attributes in each arm)
I don't, however, have a suggestion I 100% like for plain cfg
applied to an item. The cfg-if crate lets you write, effectively, if cfg!(...) { ... } else if cfg!(...) { ... } else { ... }
at file scope, and abstractly I think that's a nice notation, but I'm worried that people would immediately want to generalize it to arbitrary (const) controlling expressions and beyond, and maybe we don't want to let that genie out of its bottle?