Codegen and Optimization Flags
rustc -C flags control backend code generation: optimization level, debuginfo, codegen units, LTO, panic strategy, overflow checks, CPU features, linker behavior, and related artifact choices.
What it is
Codegen flags are passed with -C or --codegen.
They affect the generated machine code, emitted debug information, link behavior, and compile-time tradeoffs.
The rustc book recommends checking rustc -C help for the options supported by the exact compiler in use.
Common options include opt-level, debuginfo, debug-assertions, overflow-checks, codegen-units, lto, panic, target-cpu, target-feature, linker, and link-arg.
Cargo profile settings are usually the right interface for application crates.
How it works
-O is shorthand for -C opt-level=3.
-g is shorthand for -C debuginfo=2.
opt-level=0 is the default and enables cfg(debug_assertions).
Higher optimization levels can improve runtime performance but increase compile time and may reduce debuggability.
codegen-units trades compile parallelism against optimization opportunity.
More units can compile faster; codegen-units=1 can improve generated code at the cost of compile time.
lto enables link-time optimization across crate boundaries.
overflow-checks controls runtime integer overflow checks; if unspecified, it follows debug assertions.
Some flags are target-sensitive.
target-feature is explicitly risky because enabling CPU features can make the binary invalid on CPUs that lack those instructions.
Flags can also interact.
For example, LTO needs compatible bitcode availability, panic strategy must match how libraries are linked in some contexts, and linker arguments depend on the target linker flavor.
Cargo profiles encode the common, repeatable choices, while direct -C flags are best for experiments or non-Cargo build systems.
The compiler only promises that a codegen option is accepted by the exact compiler and target you are using.
Check rustc -C help, rustc --print target-cpus, and rustc --print target-features rather than copying values from another platform.
Example
fn sum_squares(values: &[u64]) -> u64 {
values.iter().map(|value| value * value).sum()
}
fn main() {
let values = [1, 2, 3, 4];
println!("{}", sum_squares(&values));
}Example direct compile commands:
rustc --edition=2024 -C opt-level=3 main.rs
rustc --edition=2024 -C debuginfo=2 -C overflow-checks=yes main.rsThe Cargo-profile equivalent is more reproducible for projects:
[profile.release]
opt-level = 3
codegen-units = 1
lto = "thin"
debug = "line-tables-only"
panic = "abort"Use a profile when the setting is part of the crate’s supported build mode; use direct -C flags when inspecting or integrating with another build system.
Common errors
Some combinations are rejected before code generation:
error: options `-C embed-bitcode=no` and `-C lto` are incompatibleFix it by removing one of the flags or letting Cargo choose compatible defaults for the selected profile.
Invalid CPU or feature names are target-specific:
error: incorrect value `native-ish` for codegen option `target-cpu`or:
warning: unknown and unstable feature specified for `-Ctarget-feature`Check the exact compiler with rustc --print target-features --target <triple> and avoid enabling features unless every deployment CPU supports them.
Linker arguments often fail outside Rust:
error: linking with `cc` failed: exit status: 1Read the linker output, confirm the target linker flavor, and prefer Cargo target configuration for persistent linker choices.
Best practice
- ✅ Prefer Cargo profiles for repeatable project optimization settings; see Profiles and Optimization Settings.
- ✅ Use direct
-Cflags for experiments, build-system integration, or one-off investigation. - ✅ Measure before and after changing
opt-level,codegen-units, orlto. - ✅ Keep
debuginfoand frame-pointer choices aligned with observability and profiling needs. - ✅ Treat
target-cpuandtarget-featureas deployment contracts. - ✅ Prefer
lto = "thin"as a first release-build experiment before paying the full compile-time cost of fat LTO. - ✅ Keep overflow checks enabled in test and debug builds; decide release behavior deliberately instead of inheriting assumptions.
- ✅ Record non-default panic, linker, relocation, or CPU settings in release documentation.
Pitfalls
- ⚠️ Assuming
-Ois always best; size, compile time, startup time, and debuggability may matter more. - ⚠️ Turning off overflow checks to hide bugs; see Integer Overflow and Relying on Integer Overflow.
- ⚠️ Combining
-C embed-bitcode=nowith LTO in direct rustc usage; that combination is invalid. - ⚠️ Passing linker flags without understanding target linker flavor.
- ⚠️ Applying
RUSTFLAGSglobally and accidentally changing dependencies, tests, build scripts, or proc macros. - ⚠️ Benchmarking one
target-cpu=nativebinary and shipping it to machines with different CPU features. - ⚠️ Stripping symbols so aggressively that production crash reports and profilers become much less useful.
See also
The rustc Compiler · Target Triples · Profiles and Optimization Settings · Integer Overflow · Panic Unwinding and Abort · Relying on Integer Overflow · Inspecting rustc Configuration · Cargo.toml Manifest · MSRV Policy · Editions & Compiler
Sources
- The rustc book, “Codegen Options” — rustc-book, https://doc.rust-lang.org/rustc/codegen-options/index.html
- The rustc book, “
-C/--codegen” — rustc-book, https://doc.rust-lang.org/rustc/command-line-arguments.html#-c—codegen-code-generation-options
