Question
Why Rust May Not Optimize Non-Aliasing Mutable References Yet
Question
In C, pointer aliasing can prevent certain optimizations because the compiler must preserve correct behavior even when two pointers refer to the same memory.
For example:
void adds(int *a, int *b) {
*a += *b;
*a += *b;
}
When compiled with Clang using optimizations, the compiler may reload *b and store to *a twice, because a and b might alias.
If we add restrict:
void adds(int *restrict a, int *restrict b) {
*a += *b;
*a += *b;
}
then the compiler can assume the pointers do not alias and optimize the function more aggressively.
In Rust, mutable references are supposed to be exclusive, so this code seems like it should allow the same optimization:
#![crate_type = "staticlib"]
#[no_mangle]
fn adds(a: &mut i32, b: &mut i32) {
*a += *b;
*a += *b;
}
However, the generated machine code may still look like the non-restrict C version.
Why does this happen? Is it because the Rust compiler has not yet fully applied aliasing-based optimizations, or because there are situations where two &mut references could still alias?
Short Answer
By the end of this page, you will understand how Rust's &mut references relate to aliasing, why non-aliasing guarantees do not always become immediate machine-code optimizations, and how compiler backends like LLVM, undefined behavior rules, and code generation details affect the final result.
Concept
Rust's &mut T means exclusive mutable access: while a mutable reference is active, no other reference is allowed to access the same value in a conflicting way. Conceptually, that sounds similar to C's restrict, because both ideas help the compiler reason about aliasing.
However, there are a few important details:
&mut is a language rule, not just a syntax hint
In safe Rust, creating two simultaneously active &mut references to the same location is forbidden. If that happens through unsafe code, the program may have undefined behavior.
That means the compiler is allowed to assume Rust's aliasing rules are respected.
But optimization depends on how those rules are represented internally
Rust does not optimize code directly into assembly by itself. rustc lowers Rust code into LLVM IR, and LLVM performs many optimizations from there. For LLVM to use the non-aliasing property, Rust must communicate the right information in a form LLVM understands, such as attributes like noalias.
If that information is missing, conservative, limited to certain contexts, or not useful enough for a given optimization pass, LLVM may generate less optimized code even though the source-level rules would permit better code.
&mut and restrict are similar, but not identical concepts
They are related, but they are not a perfect one-to-one match:
Mental Model
Think of &mut as giving one person the only key to a storage locker.
- If Alice has
&mut locker, nobody else is supposed to have a key at the same time. - Because of that, Alice can safely rearrange the contents without worrying that someone else changes them behind her back.
Now imagine Alice tells a warehouse robot to optimize her work. The robot can only act on information written on the shipping form.
- Rust's rules say: "Alice is the only one with the key."
- But the optimizer only sees what is encoded in the intermediate representation.
- If that exclusivity is not fully written down in a way the robot understands, the robot behaves cautiously.
So the important distinction is:
- language guarantee:
&mutis exclusive - optimizer usage: the backend must actually know and exploit that fact
Rust can guarantee exclusivity at the language level even when the generated code is still conservative.
Syntax and Examples
Core idea in Rust
A mutable reference gives exclusive mutable access:
fn increment(x: &mut i32) {
*x += 1;
}
This means while x: &mut i32 is active, Rust assumes no other active reference is mutating or observing the same value in a conflicting way.
Example that suggests a non-alias optimization
fn adds(a: &mut i32, b: &mut i32) {
*a += *b;
*a += *b;
}
A human might rewrite this mentally as:
fn adds(a: &mut i32, b: &mut i32) {
let temp = *b;
*a += temp * 2;
}
This is valid if a and b do not alias.
Why Rust source permits this reasoning
Calling the function like this is fine:
Step by Step Execution
Consider this Rust function:
fn adds(a: &mut i32, b: &mut i32) {
*a += *b;
*a += *b;
}
Suppose we call it like this:
let mut x = 5;
let mut y = 2;
adds(&mut x, &mut y);
Step-by-step behavior
Initial values:
x = 5y = 2
Inside the function:
- Read
*a→ readsx, so value is5 - Read
*b→ readsy, so value is2 - Compute
5 + 2 = 7
Real World Use Cases
Aliasing information matters in many real programs:
Numeric and scientific code
When processing arrays, matrices, or vectors, the compiler can generate faster code if it knows two mutable references do not overlap.
fn scale_and_add(a: &mut i32, b: &mut i32) {
*a += *b * 4;
}
In bigger loops, this can affect vectorization and register reuse.
Parsing and data transformation
When updating one field based on another, non-aliasing assumptions help the compiler avoid unnecessary memory reloads.
Systems programming
Rust is often used where predictable performance matters. Borrowing rules provide useful guarantees that can, in principle, enable strong optimizations.
APIs with input/output buffers
A function that writes to one buffer and reads from another is easier to optimize if the compiler knows the buffers do not overlap.
In-place mutation without accidental overlap
Rust's borrowing rules help developers write APIs where mutation is explicit and aliasing bugs are prevented early, even before optimization enters the picture.
Real Codebase Usage
In real Rust codebases, developers rely on the semantic guarantee of &mut more than on any single micro-optimization.
Common patterns
Guarded mutation
fn update(score: &mut i32, delta: i32) {
if delta == 0 {
return;
}
*score += delta;
}
Exclusive access makes mutation straightforward and safe.
Splitting data into non-overlapping mutable pieces
fn swap_first_two(items: &mut [i32]) {
if items.len() < 2 {
return;
}
items.swap(0, 1);
}
Libraries often provide safe APIs like split_at_mut specifically to produce two guaranteed non-overlapping mutable slices.
fn add_edges(values: &mut [i32]) {
values.() < {
;
}
(left, right) = values.();
left[] += right[];
}
Common Mistakes
Mistake 1: Assuming missing optimization means aliasing is allowed
A function may compile to conservative machine code even when Rust's rules guarantee no aliasing.
fn adds(a: &mut i32, b: &mut i32) {
*a += *b;
*a += *b;
}
If the compiler reloads *b, that does not prove a and b may alias in safe Rust.
Mistake 2: Confusing safe Rust rules with unsafe behavior
This is invalid in terms of Rust's aliasing model, even if you can write it with unsafe:
unsafe fn broken(p: *mut i32) {
let a = &mut *p;
let b = &mut *p;
*a += 1;
*b += 1;
}
Avoid creating multiple active &mut references to the same memory.
Mistake 3: Thinking &mut and C are exactly the same
Comparisons
| Concept | What it means | Compiler impact | Same as Rust &mut? |
|---|---|---|---|
Rust &mut T | Exclusive mutable reference | Can permit non-alias assumptions if conveyed to backend | Not exactly, but closely related |
Rust &T | Shared immutable reference | Usually no mutation through it, but aliasing is allowed | No |
C pointer int * | Ordinary pointer | Compiler must assume aliasing unless proven otherwise | No |
C int *restrict | Promise that accesses do not alias in the relevant scope | Enables stronger optimization | Similar in effect, not identical in model |
Cheat Sheet
Quick facts
&mut Tmeans exclusive mutable access.- In safe Rust, two active
&mutreferences to the same location are not allowed. - If
unsafecode creates aliasing&mutreferences, behavior can be undefined. - A missing optimization does not mean aliasing is permitted.
rustcrelies on LLVM for many low-level optimizations.- LLVM can optimize better only if aliasing information is correctly represented and used.
Mental shortcut
- Language rule:
&mutis exclusive. - Codegen reality: exclusivity must survive lowering into optimizer-friendly metadata.
Typical valid reasoning
fn adds(a: &mut i32, b: &mut i32) {
*a += *b;
*a += *b;
}
Because a and b cannot alias in safe Rust, this is semantically equivalent to:
fn adds(a: & , b: & ) {
= *b;
*a += t * ;
}
FAQ
Does Rust guarantee that two &mut references cannot alias?
Yes, in safe Rust. If aliasing &mut references are created through unsafe code, that violates Rust's rules and may cause undefined behavior.
If &mut is exclusive, why doesn't the compiler always optimize more aggressively?
Because the optimization depends on whether that exclusivity is preserved and exploited in the compiler pipeline, especially in LLVM IR and later passes.
Is Rust's &mut the same as C's restrict?
Not exactly. They are similar in optimization intent, but they come from different language semantics and are not a direct one-to-one feature match.
Does unsafe mean aliasing &mut references becomes valid?
No. unsafe allows you to perform unchecked operations, but it does not make invalid aliasing legal. It just makes it your responsibility.
Can compiler versions change this behavior?
Yes. Code generation and optimization can differ across rustc and LLVM versions.
Does #[no_mangle] affect optimization?
It can affect how the function is treated, especially around visibility, linkage, and inlining opportunities.
Mini Project
Description
Build a small Rust example that demonstrates how Rust prevents aliasing of mutable references in safe code, while still allowing efficient mutation of separate values. This project helps you connect borrow rules with optimization-friendly semantics.
Goal
Write a program that safely updates two separate integers through &mut references and shows a borrow-checker error when trying to alias them in safe Rust.
Requirements
- Create a function that takes two
&mut i32parameters and updates one using the other. - Call the function with two different variables and print the result.
- Include a commented-out example showing that passing
&mut xtwice is rejected. - Add a second function that uses a temporary variable to make the non-aliasing logic explicit.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Default Function Arguments in Rust: What to Use Instead
Learn how Rust handles default function arguments, why they are not supported, and practical patterns to achieve similar behavior.
Fixing Rust "linker 'cc' not found" on Debian in WSL
Learn why Rust shows "linker 'cc' not found" on Debian in WSL and how to fix it by installing the required C build tools.