-----Cargo.toml-----
[package]
name = "tool_lib"
version = "0.1.0"
edition = "2024"
[features]
default = []
string_utils = ["math_utils"]
math_utils = []
[dependencies]
-----math.rs-----
#[cfg(feature = "math_utils")]
pub mod math_utils {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
-----string.rs-----
#[cfg(feature = "string_utils")]
pub mod string_utils {
#[cfg(feature = "math_utils")]
use super::math_utils;
pub fn reverse_string(s: &str) -> String {
#[cfg(feature = "math_utils")]
let _result = math_utils::add(1, 2);
s.chars().rev().collect()
}
}
Develop a tool library, in order for third parties to use on demand, open the feature, at this time brings a problem, that is, inside the tool library, when referring to each other, you have to look like the following, everywhere is #[cfg(feature = "xx")], too chicken, such as the following code:
Heya, it is quite repetitive and interleaved isn't it. I'm sure many people have run into this problem, and there are some ways to reduce the sprawl:
cfg-if lets you put multiple things within a block, without actually creating a block (so you can put use .. statements in there for example).
Sometimes I switch between the interleaving (like what you show above), to code repetition:
#[cfg(all(feature = "math_utils", not(feature = "string_utils")))]
mod math_utils;
#[cfg(all(not(feature = "math_utils"), feature = "string_utils"))]
mod string_utils;
// This would contain code copied from both `math_utils` and `string_utils`
//
// which means you need to update code in multiple files instead of one,
// but within each file, it may read nicer.
#[cfg(all(feature = "math_utils", feature = "string_utils"))]
mod math_and_string_utils;
Sometimes I use the qualified syntax to reduce the separate use statements:
#[cfg(feature = "string_utils")]
pub mod string_utils {
- #[cfg(feature = "math_utils")]
- use super::math_utils;
pub fn reverse_string(s: &str) -> String {
#[cfg(feature = "math_utils")]
- let _result = math_utils::add(1, 2);
+ let _result = super::math_utils::add(1, 2);
s.chars().rev().collect()
}
}
This way there's fewer #[cfg(..)] statements, but the code does get a bit longer inline.
Even if this is barely achieved, but still feel that the official should simplify this design, rust for people who want to enter the pit, the learning cost is already very high, or to simplify as much as possible
-----Cargo.toml-----
[package]
name = "tool_lib"
version = "0.1.0"
edition = "2024"
[features]
default = []
string_utils = ["math_utils"]
math_utils = []
[dependencies]
-----math.rs-----
#[cfg(feature = "math_utils")]
pub mod math_utils {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn sub(a: i32, b: i32) -> i32 {
a - b
}
}
-----string.rs-----
#[cfg(feature = "string_utils")]
pub mod string_utils {
#[cfg(feature = "math_utils")]
use super::math_utils;
pub fn a_string(){
let res = math_utils::add(1, 2);
println!("---{}", res );
}
pub fn b_string() {
let res = math_utils::sub(5, 3);
println!("---{}", res );
}
}
For example, in a module, after the feature is marked in one place, it can be used in other places, which is not much simpler
I don't find this repetitive at all. And I don't understand what this has to do with being a chicken.
In Rust attributes apply to the following statement or expression only.
It would be confusing, if they did anything else.
Note that what you want to do can be achieved by using #![...] at the top of the module already and it is semantically different from your code.
In your example, the usage of math_utils in string_utils is entirely optional and changes the module's behavior, i.e. produces side-effects, regardless whether this might be a good or bad idea.