Attributes and Conditional Compilation

Conditional Modules

Conditional modules use #[cfg(...)] on mod declarations and pub use re-exports to organize platform-specific or feature-gated implementations into clean, self-contained files without cluttering individual functions with fine-grained #[cfg] blocks.

Pattern 1: Platform-Dispatched Private Backend Modules (mod imp)

The standard library (std::sys) and cross-platform crates (tokio, mio, rustix) isolate OS differences by placing each platform's implementation in its own module (unix.rs, windows.rs) that exposes an identical internal interface, and then re-exporting the active module under a single private name (use self::imp::*;):

Cross-Platform Module Architecture:

  src/fs_watcher.rs (Public Cross-Platform Façade)
    +-- #[cfg(unix)]    mod imp { /* kqueue / inotify implementation */ }
    +-- #[cfg(windows)] mod imp { /* ReadDirectoryChangesW implementation */ }
    |
    +-- pub struct Watcher(imp::OsWatcher);  <-- Unified public type!
#[cfg(unix)]
mod imp {
    pub struct NativePoller {
        fd: i32,
    }

    impl NativePoller {
        pub fn new() -> Self {
            Self { fd: 4 }
        }

        pub fn backend_name(&self) -> &'static str {
            "unix-poll (fd-based)"
        }

        pub fn raw_id(&self) -> usize {
            self.fd as usize
        }
    }
}

#[cfg(windows)]
mod imp {
    pub struct NativePoller {
        iocp_handle: usize,
    }

    impl NativePoller {
        pub fn new() -> Self {
            Self { iocp_handle: 0x100 }
        }

        pub fn backend_name(&self) -> &'static str {
            "windows-iocp"
        }

        pub fn raw_id(&self) -> usize {
            self.iocp_handle
        }
    }
}

// Public cross-platform wrapper delegates to `imp::NativePoller` with ZERO `#[cfg]`
// annotations inside the public methods:
pub struct Poller {
    inner: imp::NativePoller,
}

impl Poller {
    pub fn new() -> Self {
        Self {
            inner: imp::NativePoller::new(),
        }
    }

    pub fn info(&self) -> String {
        format!(
            "backend = {}, handle_id = {}",
            self.inner.backend_name(),
            self.inner.raw_id()
        )
    }
}

fn main() {
    let poller = Poller::new();
    println!("{}", poller.info());
}
backend = unix-poll (fd-based), handle_id = 4

Pattern 2: Test-Only Modules (#[cfg(test)] mod tests)

Placing #[cfg(test)] above mod tests { ... } ensures that unit tests and test-only helper functions are compiled only when running cargo test (rustc --test), and are completely excluded from normal cargo build and --release artifacts.

Pattern 3: Custom Module Source Paths with #[path = "..."]

When mapping multiple conditional modules to files inside a platform subdirectory (src/sys/unix.rs vs. src/sys/windows.rs), the #[path = "..."] attribute overrides the default file lookup path for mod sys;:

// #[cfg_attr(unix, path = "sys/unix.rs")]
// #[cfg_attr(windows, path = "sys/windows.rs")]
// mod sys;