Search code examples
rustrust-cargo

Using std::os::linux and std::os::windows in a cross-platform application


I want to create a multi-platform application that accesses the file system, and while I can use std::fs, I've found that the OS specific crates simplify the code a bit. So, I wrote up some code using std::os::windows::fs, and it worked as expected.

My issue now, is that I want to write code for both Windows and Linux. To do this, I've created separate functions that in theory will use std::os::windows and std::os::linux respectfully. However when trying to compile on Linux for Linux, I came across the error that std::os::windows was not a recognised crate.

My question is, is there any way for me to either use std::os::windows on Linux and vice versa, or only compile specific chunks of code depending on the operating system cargo is used on?

I know this is a bit of a stupid question, considering the fact that I could just write separate modules for different OSes, but I do want to try and contain everything in one module.

My current Code is something along the lines of:

pub fn run(os: &str, path: &str){
    let files = fs::read_dir(path).unwrap_or_else(|error: std::io::Error| {
        println!("Error: {error}");
        process::exit(1);
    });

    // The 'os' variable is obtained from std::env::consts::OS
    if os == "windows" {
        // Use std::os::windows in the following function
        printDir_win(files);
    } else if os == "linux" {
        // Use std::os::linux in the following function
        printDir_lin(files);
    } else {
        println!("{}OS not recognised: defaulting to std::fs methods{}","\x1b[31m","\x1b[0m");
        printDir(files);
    }
}

Solution

  • Use the #[cfg(…) attribute for conditional compilation depending on the target OS. That way the compiler won't try to compile code that targets a different OS:

    pub fn run(path: &str){
        let files = fs::read_dir(path).unwrap_or_else(|error: std::io::Error| {
            println!("Error: {error}");
            process::exit(1);
        });
    
        // The 'os' variable is obtained from std::env::consts::OS
        #[cfg (target_os = "windows")]
        {
            // Use std::os::windows in the following function
            use std::os::windows::*;
            printDir_win(files);
        }
        #[cfg (target_os = "linux")]
        {
            // Use std::os::linux in the following function
            use std::os::linux;
            printDir_lin(files);
        }
        #[cfg (not (any (target_os = "windows", target_os = "linux")))]
        {
            println!("{}OS not recognised: defaulting to std::fs methods{}","\x1b[31m","\x1b[0m");
            printDir(files);
        }
    }
    

    Note that from an organization standpoint, it is considered good practice to group your platform-specific code in a module that abstracts the differences behind a common API:

    #[cfg (target_os = "windows")]
    mod platform {
        use std::os::windows::*;
        pub fn print_dir (files: std::fs::ReadDir) {
            unimplemented!()
        }
    }
    #[cfg (target_os = "linux")]
    mod platform {
        use std::os::linux::*;
        pub fn print_dir (files: std::fs::ReadDir) {
            unimplemented!()
        }
    }
    #[cfg (not (any (target_os = "windows", target_os = "linux")))]
    mod platform {
        pub fn print_dir (files: std::fs::ReadDir) {
            unimplemented!()
        }
    }
    

    Then the rest of your code doesn't need to check the OS everywhere to know which function to call:

    pub fn run(path: &str){
        let files = std::fs::read_dir(path).unwrap_or_else(|error: std::io::Error| {
            println!("Error: {error}");
            std::process::exit(1);
        });
        platform::print_dir (files);
    }
    

    Playground