How can I conditionally include a `rs` file on given `target_os`?

Hi, guys.

I have a tiny Rust project, and I have a linux_specific_functions.rs file which should only be compiled in the Linux platform.
In other platforms, e.g. Windows, and macOS, it should be excluded. So the Linux-specific functions can only be found and used in Linux.

How can I do that? the cfg macro/attribute seems only available at the function level.

More specifically, the Linux platform have a 3rd party dependency which I declared in Cargo.toml:

[target.'cfg(linux)'.dependencies]
foo = "1.0"
#[cfg(target_os = "linux")]
mod linux_specific_mod;

#[cfg(target_os = "windows")]
mod windows_specific_mod;

should work fine. I'm not sure the Cargo.toml you've shown would work though?

The reference says

unix is set if target_family = "unix" is set and windows is set if target_family = "windows" is set.

I'm not sure there's a plain cfg(linux), based on the reference at least.

1 Like

cfg(linux)[1] and cfg(target_os = "linux") are different. You can try

/* Cargo.toml 
[target.'cfg(target_os = "linux")'.dependencies]
foo = "1.0"
*/

#[cfg(target_os = "linux")]
mod linux_specific_functions;

  1. There is no cfg(linux), only cfg(unix) and cfg(windows) are valid in that form. ↩︎

1 Like

Great, I've tried this. It works fine.

And one more question, how can I call different code path for different target_os?

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = ...;

// This won't work
    if cfg!(linux) {
        linux_main(args).await
    } else {
        other_main(args).await
    }
}
error[E0425]: cannot find function `other_main` in this scope
   --> server/src/main.rs:210:9
    |
210 |         other_main(args).await
    |         ^^^^^^^^^^ not found in this scope

I'm currently on the Linux platform.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(target_os = "linux")]
    linux_main().await
    #[cfg(not(target_os = "linux"))]
    other_main().await
}

Or use if cfg!(target_os = "linux") instead of if cfg!(linux).
Again Rust recognizes cfg(unix) and cfg(windows) by default.

1 Like

FYI, the last statement as the result should have no further tokens.

rustcc will complain your code with:

error: expected `;`, found `#`
   --> server/src/main.rs:208:31
    |
208 |     linux_main(args).await
    |                               ^ help: add `;` here
209 |
210 |     #[cfg(not(target_os = "linux"))]
    |     - unexpected token

error: unexpected token
   --> server/src/main.rs:210:5
    |
210 |     #[cfg(not(target_os = "linux"))]
    |     ^

The following code will work.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(target_os = "linux")]
    linux_main().await?;
    #[cfg(not(target_os = "linux"))]
    other_main().await?;
    Ok(())
}

Again Rust recognizes cfg(unix) and cfg(windows) by default.

So the cfg(unix) and cfg(windows) are the only two built-in configs in rustcc?

I haven't seen cfg(linux) in the Rust doc.

Yes

clippy will warn you of that:

error: operating system used in target family position
 --> src/main.rs:2:5
  |
2 |     #[cfg(linux)]
  |     ^^^^^^-----^^
  |           |
  |           help: try: `target_os = "linux"`
  |
  = help: did you mean `unix`?
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
  = note: `#[deny(clippy::mismatched_target_os)]` on by default

You're able to use it as a custom cfg flag, but it's not recommended in your case:

#[cfg(linux)]
// item

$ RUSTFLAGS='--cfg linux' cargo r

FYI, one convention for platform conditional modules is something like:

#[cfg(cond1)]
mod first_imp;
#[cfg(cond1)]
use first_imp as imp;
#[cfg(cond2)]
mod second_imp;
#[cfg(cond2)]
use second_imp as imp;
// each module should share a good amount of the same interface
// so that in your code you can just do
imp::foobar()
2 Likes

Excellent job, the guideline towards the answer is inspiring!

Yes that's a good practice.

A less commom way is to use cfg_attr as exemplified in the Reference.

#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod os;
3 Likes

Found a better way

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(target_os = "linux")] {
        Ok(linux_main().await?)
    }

    #[cfg(not(target_os = "linux"))] {
        Ok(other_main().await?)
    }
}

Personally I think a plain if/else with cfg!(target_os = "linux") as the condition would read better in this case. Are the Ok(...?) necessary when you write it like this?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.