Rustでプラットフォーム依存の処理を書く
#[cfg(...)] Attribute
Rustでは #[cfg(...)]
アトリビュートを使うことによって、OSやCPUに応じた条件コンパイルを行うことができる。
- Conditional Compilation
- cfgは複数条件指定可能(OR, AND, NOT)
#[cfg(any(unix, windows))]
#[cfg(all(unix, target_pointer_width = "32"))]
#[cfg(not(foo))]
- Attributes - The Rust Reference
例えば、次のように書くことでコンパイルターゲットのOSに応じたhello()
をビルドし、呼び出すことができる。
#[cfg(target_os = "windows")] fn hello() { println!("Hello, I'm Windows!"); } #[cfg(target_os = "linux")] fn hello() { println!("Hello, I'm Linux!"); } #[cfg(target_os = "macos")] fn hello() { println!("hello, I'm MacOS."); } fn main() { hello(); }
モジュールを分ける
各複数プラットフォーム向けの処理が小さい時には、前述のように1つのファイルの中にベタ書きでも問題ないが、コードが膨らんできた場合は各プラットフォーム毎にファイルを分けた方がいいだろう。
そこで、次のようなモジュール構成を考てみる。 nameモジュールを用意し、OS依存部分はnameモジュールの中に隠蔽することを目的とする。
Cargo.toml src/ ├── lib.rs └── name ├── linux.rs ├── macos.rs ├── mod.rs └── windows.rs
name/${os_dependent}.rs
OS依存の処理を name/以下の linux.rs, macos.rs, windows.rsにそれぞれ書いていく。
各モジュールはOSの名前をStringで返すだけのname()
関数を実装する。
name/linux.rs
pub fn name() -> String { "Linux".to_owned() }
name/macos.rs
pub fn name() -> String { "MacOS".to_owned() }
name/windows.rs
pub fn name() -> String { "Windows".to_owned() }
name/mod.rs
各OS依存の処理を他のモジュールから扱えるようにするために name/mod.rs を記述する。
name/mod.rs
#[cfg(target_os = "linux")] pub mod linux; #[cfg(target_os = "linux")] pub use self::linux::*; #[cfg(target_os = "macos")] pub mod macos; #[cfg(target_os = "macos")] pub use self::macos::*; #[cfg(target_os = "windows")] pub mod windows; #[cfg(target_os = "windows")] pub use self::windows::*;
ここでのmod.rsの役割は、コンパイル対象とするモジュールの切り替えと、他のモジュールへの再エキスポートだ。
コンパイル対象とするモジュールの切り替え
これは前述のように #[cfg(target_os = "linux")]
を使ってコンパイル対象のモジュールを指定する。
#[cfg(target_os = "linux")] pub mod linux;
他のモジュールへの再エクスポート
pub use
によって再エクスポートすることによって、他のモジュールからnameのOS依存モジュールを意識せずに使えるようにしている。
#[cfg(target_os = "linux")] pub use self::linux::*;
この記述がないと、nameモジュールのOS依存処理を使う時は常にモジュール名を明確に指定しなくてはならない。 これでは以下のように他のモジュールが常にOS依存を気にしたコードを書かなくてはならず、非常によろしくない。
mod name; let os_name = name::linux::name();
pub use
することによって、OS依存モジュール名を省略して使用することができるようになり、他のモジュールからOS依存を気にせずに使用できるようになる。
// mod.rsでpub use self::linux::*; mod name; let os_name = name::name();
lib.rs
最後に、今まで定義したnameモジュールを使ったlib.rsを次に記述する。
lib.rs
mod name; pub fn greeting() { println!("Hello, I'm {}.", name::name()); } // -> Hello, I'm ${YOUR_OS}. #[cfg(test)] mod tests { use super::*; #[cfg(target_os = "linux")] #[test] fn test_linux() { assert_eq!("Linux", name::name()); } #[cfg(target_os = "macos")] #[test] fn test_linux() { assert_eq!("MacOS", name::name()); } #[cfg(target_os = "windows")] #[test] fn test_linux() { assert_eq!("Windows", name::name()); } }
Linux上でテストを実行すると、name()は"Linux"の文字列を返してくれているのが確認できる。
$ cargo test Compiling hello v0.1.0 (/home/ryo/code/sandbox/rust/scraps/target_os) Finished dev [unoptimized + debuginfo] target(s) in 0.75s Running target/debug/deps/hello-69d1a57a65d70c5b running 1 test test tests::test_linux ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests hello running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out