I have seen people write things like this often:
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use self::unix::*;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use self::windows::*;
… and commonly even more variations …
Figure 1: multiple mod
and use
statements in order to switch between platform‐specific implementations.
This can get unwieldy.
There are two features that you can combine to make this simpler and smaller:
Here’s how it goes when combined:
#[cfg_attr(unix, path = "unix.rs")]
#[cfg_attr(windows, path = "windows/mod.rs")]
… and perhaps even more variations …
mod platform;
pub use self::platform::*;
Figure 2: just one mod
statement decorated by multiple attributes in order to switch between implementations, and one use
statement.
And so in this example, on Unix platforms it’ll load unix.rs as the platform
module, while on Windows it’ll load windows/mod.rs [FYI, if you went for windows/mod.rs because windows
contained other modules inside it, know that from Rust 1.30 onwards you can actually have a file named windows.rs beside that windows/ subdirectory, instead of it having to be windows/mod.rs. The Rust Reference even recommends switching to this new style, though I’m not sold on it.] as the platform
module, and so in either case it can subsequently do a reexport if desired.
Mind you, for complex enough specifications, even the single mod version can still be messy:
#[cfg_attr(
all(
target_arch = "wasm32",
any(target_os = "emscripten", target_os = "unknown"),
),
path = "wasm32/mod.rs"
)]
#[cfg_attr(windows, path = "windows/mod.rs")]
#[cfg_attr(
not(any(
all(
target_arch = "wasm32",
any(target_os = "emscripten", target_os = "unknown"),
),
windows,
)),
path = "common/mod.rs"
)]
mod imp;
Figure 3: a very slightly unwieldy situation seen in os_str_bytes.
(While thinking about os_str_bytes, I wonder what the chances are of ever convincing the Rust core and libs teams to make cross‐platform OsStr
→ [u8]
and OsString
→ Vec<u8>
conversions (ideally plus checked and unchecked conversions for the other way). As it stands, OsStr::to_str
forces them to be roughly UTF-8 already, and the current implementation depends on compatibility, which I can’t honestly imagine ever changing, but the docs stubbornly insist on being mysterious, and the source code stubbornly insists of OsStr
, OsString
, Path
and PathBuf
that their “representation and layout are considered implementation details, are not documented and must not be relied upon”. But such musings as these don’t fit in a figure caption. I can already just tell that half the comments about this article are going to be about this little side point.)
And truly they can get much worse than that; the worst cases I’ve seen might even be enough for me to reach for cfg-if (which I am loath to do—I reckon the significant majority of its uses are unwarranted) and separate mod
statements, rather than using #[cfg_attr]
.