# Understanding Targets in Rust
Imagine you’re building a house — you wouldn’t use the same blueprint for every location. Similarly, in Rust, targets act as blueprints that tell the compiler how to adapt your code for different environments. Whether you’re creating an executable program, a reusable library, or compiling for specialized platforms like WebAssembly, targets ensure your code runs smoothly wherever it’s deployed. Understanding how to configure and manage targets is crucial for organizing your project efficiently and unlocking Rust’s full potential across diverse platforms. In this guide, we’ll explore what targets are, the different types of targets available in Rust, and how they work in practice. What is a Target? At its core, a target in Rust specifies the platform or architecture for which your code will be compiled. This includes details like the operating system, CPU architecture, and even custom configurations for embedded systems or experimental platforms. By defining a target, you’re essentially telling the Rust compiler: “Here’s where my code needs to run—make it happen!” Targets play a key role in both simple and complex projects. For example: If you’re building a command-line tool, you’ll likely use a binary target. If you’re developing a library for others to use, you’ll rely on a library target. If you’re targeting WebAssembly (WASM) for web applications, you’ll specify a WASM-compatible target. Let’s dive into the different types of targets and how they function. Binary Targets Binary targets are used to create executable programs. By default, if your project has a main.rs file in the src/ directory, Cargo treats it as a binary target. When you run cargo build or cargo run, Cargo compiles this file into an executable. You can also define multiple binaries by adding .rs files to the src/bin/ directory. For example: src/bin/app1.rs → Produces an executable named app1. src/bin/app2.rs → Produces an executable named app2. This flexibility is particularly useful for projects with multiple entry points, such as CLI tools with subcommands. Library Targets If your project is a library rather than an executable, Cargo looks for a lib.rs file in the src/ directory. Libraries are designed to provide reusable code that other programs or libraries can depend on. When you compile a library, Rust generates a .rlib file — an intermediate format used during compilation to link libraries into executables or other libraries. The .rlib file is an archive format that contains the compiled code (object files) and metadata for the library. For example: // src/lib.rs pub fn greet() { println!("Hello from the library!"); } Other crates can then import and use the greet function by adding your library as a dependency. Test and Benchmark Targets Rust makes it easy to write tests and benchmarks directly within your codebase. These are treated as special types of targets: Unit Tests: Written inline with your code using the #[test] attribute. Integration Tests: Placed in a dedicated tests/ directory at the root of your project. These tests verify how different parts of your library or application work together. For example: // Unit test in src/lib.rs #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } } // Integration test in tests/integration_test.rs #[test] fn test_greet() { my_crate::greet(); // Calls the greet function from the library } To run tests, use cargo test. Similarly, benchmarks can be written using the #[bench] attribute (though benchmarking support is still experimental). Architecture-Specific Targets One of Rust’s standout features is its ability to cross-compile for different architectures and platforms. For example: Compile for Linux on an x86_64 machine: cargo build --target x86_64-unknown-linux-gnu Compile for WebAssembly: cargo build --target wasm32-unknown-unknown Cross-compilation is especially valuable for embedded systems, IoT devices, or when targeting non-standard platforms. To see a list of supported targets, run: rustup target list Custom Targets For advanced use cases, you can define custom targets tailored to specific requirements. Custom targets are often used in embedded systems or when working with niche hardware. They’re defined using JSON files that specify details like the CPU architecture, linker settings, and ABI (Application Binary Interface). For example, here’s a minimal custom target definition: { "llvm-target": "armv7-unknown-linux-gnueabihf", "data-layout": "...", // Omitted for brevity "arch": "arm", "target-endian": "little", "target-pointer-width": "32", "target-c-int-width": "32", "os": "linux", "executables": true } Once defined, you can use the custom target with: cargo build --target path/to/custom_target.json How Tar

Imagine you’re building a house — you wouldn’t use the same blueprint for every location. Similarly, in Rust, targets act as blueprints that tell the compiler how to adapt your code for different environments. Whether you’re creating an executable program, a reusable library, or compiling for specialized platforms like WebAssembly, targets ensure your code runs smoothly wherever it’s deployed. Understanding how to configure and manage targets is crucial for organizing your project efficiently and unlocking Rust’s full potential across diverse platforms.
In this guide, we’ll explore what targets are, the different types of targets available in Rust, and how they work in practice.
What is a Target?
At its core, a target in Rust specifies the platform or architecture for which your code will be compiled. This includes details like the operating system, CPU architecture, and even custom configurations for embedded systems or experimental platforms. By defining a target, you’re essentially telling the Rust compiler: “Here’s where my code needs to run—make it happen!”
Targets play a key role in both simple and complex projects. For example:
- If you’re building a command-line tool, you’ll likely use a binary target.
- If you’re developing a library for others to use, you’ll rely on a library target.
- If you’re targeting WebAssembly (WASM) for web applications, you’ll specify a WASM-compatible target.
Let’s dive into the different types of targets and how they function.
Binary Targets
Binary targets are used to create executable programs. By default, if your project has a main.rs
file in the src/
directory, Cargo treats it as a binary target. When you run cargo build
or cargo run
, Cargo compiles this file into an executable.
You can also define multiple binaries by adding .rs
files to the src/bin/
directory. For example:
-
src/bin/app1.rs
→ Produces an executable namedapp1
. -
src/bin/app2.rs
→ Produces an executable namedapp2
.
This flexibility is particularly useful for projects with multiple entry points, such as CLI tools with subcommands.
Library Targets
If your project is a library rather than an executable, Cargo looks for a lib.rs
file in the src/
directory. Libraries are designed to provide reusable code that other programs or libraries can depend on.
When you compile a library, Rust generates a .rlib
file — an intermediate format used during compilation to link libraries into executables or other libraries. The .rlib
file is an archive format that contains the compiled code (object files) and metadata for the library.
For example:
// src/lib.rs
pub fn greet() {
println!("Hello from the library!");
}
Other crates can then import and use the greet
function by adding your library as a dependency.
Test and Benchmark Targets
Rust makes it easy to write tests and benchmarks directly within your codebase. These are treated as special types of targets:
-
Unit Tests: Written inline with your code using the
#[test]
attribute. -
Integration Tests: Placed in a dedicated
tests/
directory at the root of your project. These tests verify how different parts of your library or application work together.
For example:
// Unit test in src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
// Integration test in tests/integration_test.rs
#[test]
fn test_greet() {
my_crate::greet(); // Calls the greet function from the library
}
To run tests, use cargo test
. Similarly, benchmarks can be written using the #[bench]
attribute (though benchmarking support is still experimental).
Architecture-Specific Targets
One of Rust’s standout features is its ability to cross-compile for different architectures and platforms. For example:
- Compile for Linux on an
x86_64
machine:
cargo build --target x86_64-unknown-linux-gnu
- Compile for WebAssembly:
cargo build --target wasm32-unknown-unknown
Cross-compilation is especially valuable for embedded systems, IoT devices, or when targeting non-standard platforms. To see a list of supported targets, run:
rustup target list
Custom Targets
For advanced use cases, you can define custom targets tailored to specific requirements. Custom targets are often used in embedded systems or when working with niche hardware. They’re defined using JSON files that specify details like the CPU architecture, linker settings, and ABI (Application Binary Interface).
For example, here’s a minimal custom target definition:
{
"llvm-target": "armv7-unknown-linux-gnueabihf",
"data-layout": "...", // Omitted for brevity
"arch": "arm",
"target-endian": "little",
"target-pointer-width": "32",
"target-c-int-width": "32",
"os": "linux",
"executables": true
}
Once defined, you can use the custom target with:
cargo build --target path/to/custom_target.json
How Targets Work in Practice
Now that we’ve covered the different types of targets, let’s explore how they work in real-world scenarios.
Building and Running Targets
- To build a specific binary target:
cargo run --bin app1
- To build for a specific architecture:
cargo build --target wasm32-unknown-unknown
Managing Targets in RustRover
The JetBrains RustRover IDE simplifies working with targets by providing a dedicated “Targets” section in the Cargo window. From here, you can:
- Switch between binary, library, and test targets.
- Build or run your code for specific configurations without manually specifying them in the terminal.
This feature is particularly helpful for large projects with multiple binaries or when targeting different platforms.
Conclusion
Targets are a powerful feature of Rust that enable developers to adapt their code for a wide range of environments and use cases. Whether you’re building executables, libraries, or cross-compiling for specialized platforms, understanding how to configure and manage targets is essential for efficient development.
By mastering the concepts outlined in this guide—binary and library targets, testing and benchmarking, architecture-specific builds, and custom configurations—you’ll be well-equipped to tackle any project with confidence. So go ahead, experiment with different targets, and unlock the full versatility of Rust!
Ben Santora - March 2025