Course contentsShow
← Rust
Lesson 1620 of 1,91014. System Integration and EcosystemFree lesson

Platform-Specific Build Logic

You will implement conditional compilation logic to support multiple OS architectures in a single FFI project.

In this lesson, you will learn how to orchestrate cross-platform FFI builds by leveraging build.rs scripts and cfg attributes to handle varying system architectures.

When interfacing with C libraries, your Rust code often needs to adjust its linkage, include paths, or even the underlying data representation based on the target OS or CPU architecture. While cfg attributes work well within your src/ files, some decisions—like which C library binary to link against or which architecture-specific header files to parse—must happen before the compiler even looks at your Rust source. This is the domain of the build.rs (Build Script).

Controlling the Build Pipeline

A build script is a Rust file executed by Cargo before your main crate is compiled. By utilizing the std::env module, your script can detect the target environment and communicate instructions back to Cargo using "build instructions" printed to standard output.

For example, if you need to link different static libraries based on the OS:

// build.rs
fn main() {
    let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();

    if target_os == "macos" {
        println!("cargo:rustc-link-lib=framework=CoreFoundation");
    } else if target_os == "linux" {
        println!("cargo:rustc-link-lib=static=stdc++");
    }

    // Tell Cargo to re-run this script if our platform-specific logic changes
    println!("cargo:rerun-if-env-changed=TARGET");
}

Bridging the Gap with cfg

Once your build script has prepared the environment, you use the #[cfg] attribute to ensure your FFI bindings respect those platform differences. If your FFI code relies on specific integer sizes (like long in C), you can define custom conditional flags in your build script using println!("cargo:rustc-cfg=feature_name");. This allows your Rust code to conditionally compile specific struct layouts:

#[cfg(target_arch = "x86_64")]
#[repr(C)]
struct RawPointer { ptr: *mut u64 }

#[cfg(target_arch = "aarch64")]
#[repr(C)]
struct RawPointer { ptr: *mut u32 }

By decoupling architectural discovery from the main crate logic, you maintain a clean, portable FFI interface that adapts seamlessly to the host environment.

Key Takeaway: Use build.rs to detect environment metadata and emit cargo: instructions to dynamically configure the compilation environment, then pair this with #[cfg] attributes to enforce platform-specific memory layouts and linkage requirements.