Question
I saw this Rust code:
fields.sort_by_key(|&(_, ref field)| field.tags().into_iter().min().unwrap());
let fields = fields;
What does the line let fields = fields; do, and why would someone write it after sorting the vector?
Short Answer
By the end of this page, you will understand that let fields = fields; in Rust creates a new binding with the same name, a feature called shadowing. You will learn how shadowing differs from assignment, why it is often used to change mutability, ownership, or type context, and why this pattern appears in real Rust code.
Concept
In Rust, this line:
let fields = fields;
does not mean “assign the variable to itself” in the usual sense.
Instead, it creates a new variable binding named fields and initializes it using the old fields.
This is called shadowing.
What shadowing means
When you write:
let x = 5;
let x = x;
the second x is a brand new binding. It reuses the same name, but it is not the same variable as the first one.
Rust allows this because let always creates a new binding.
Why this matters
Shadowing is useful in Rust because it lets you:
- keep the same variable name while transforming a value
- make a mutable variable effectively immutable afterward
- change the type associated with a name
- move ownership into a new binding
- limit how long an older binding remains visible
In the original example
fields.sort_by_key(|&(_, ref field)| field.tags().into_iter().().());
= fields;
Mental Model
Think of a variable name in Rust like a label attached to a box.
- First, you have a box labeled
fields. - Then Rust lets you create a new box and attach the same label
fieldsto it. - From that point on, when you say
fields, you mean the new box, not the old one.
For let fields = fields;, imagine this:
- You have a temporary workbench copy labeled
fieldsthat you are allowed to rearrange. - Once you finish rearranging it, you put the result into a clean final slot, also labeled
fields. - From then on, you use the final version.
So the code is often less about changing the value and more about changing how the program is allowed to use that value.
Syntax and Examples
The general syntax is:
let name = value;
let name = name;
This is shadowing.
Basic example
fn main() {
let x = 10;
let x = x;
println!("{}", x);
}
This prints:
10
The value stays the same, but the second let x = x; creates a new binding.
Common real use: remove mutability
fn main() {
let mut numbers = vec![3, 1, 2];
numbers.sort();
let numbers = numbers;
println!("{:?}", numbers);
}
Step by Step Execution
Consider this example:
fn main() {
let mut fields = vec![3, 1, 2];
fields.sort();
let fields = fields;
println!("{:?}", fields);
}
Step-by-step
1. Create a mutable vector
let mut fields = vec![3, 1, 2];
- A variable named
fieldsis created. - It is mutable because of
mut. - Current value:
[3, 1, 2]
2. Sort the vector in place
fields.sort();
- The vector is rearranged.
- Current value becomes
[1, 2, 3]
Real World Use Cases
Shadowing with let x = x; appears in real Rust programs for a few practical reasons.
1. Temporary mutability
A value may need to be mutable only during setup.
let mut config = load_config();
config.normalize();
let config = config;
After normalization, the developer wants to prevent further changes.
2. Ownership handoff into a final binding
Sometimes a value is built or modified in stages, then rebound as the final version.
let mut items = fetch_items();
items.sort();
let items = items;
This makes it clear that the editable phase is over.
3. Keeping the same variable name after a conversion
let data = read_input();
let data = data.trim();
let data = data.parse::<>().();
Real Codebase Usage
In real Rust codebases, shadowing is a normal and idiomatic tool.
Common pattern: mutable setup, immutable use
One of the most common uses is this pattern:
let mut data = build_data();
data.sort();
data.dedup();
let data = data;
This communicates intent clearly:
- mutation is allowed during preparation
- mutation is not expected afterward
Builder-like workflows
Developers often process values in multiple steps:
let request = request.headers(headers);
let request = request.body(body);
This keeps a single meaningful name while each step produces a new value.
Validation and parsing pipelines
let input = input.trim();
let input = if input.is_empty() { (); } { input };
= input.parse::<>()?;
Common Mistakes
1. Thinking it is a no-op
Beginners often assume this line does nothing:
let fields = fields;
But it creates a new binding. That can affect:
- mutability
- type
- ownership
- visibility of the old binding
2. Confusing shadowing with assignment
Broken understanding:
let x = 5;
let x = 6;
This is not changing the first x. It creates a second x.
Compare with assignment:
let mut x = 5;
x = 6;
Use assignment when you want to update an existing mutable variable. Use shadowing when you want a new binding.
3. Expecting mutability to carry over automatically
fn main() {
= ;
= x;
x = ;
}
Comparisons
| Concept | Syntax | Creates new binding? | Requires mut? | Can change type? |
|---|---|---|---|---|
| Shadowing | let x = x; or let x = expr; | Yes | No | Yes |
| Assignment | x = expr; | No | Yes | No |
Shadowing vs assignment
let mut x = 10;
x = 20;
- updates the same variable
- needs
mut - keeps the same type
Cheat Sheet
Quick reference
Shadowing
let x = 5;
let x = x;
- creates a new binding
- reuses the same name
- old binding is shadowed
Common use: remove mutability
let mut items = vec![3, 1, 2];
items.sort();
let items = items;
After this, items is immutable.
Assignment is different
let mut x = 1;
x = 2;
- modifies existing variable
- requires
mut - cannot change type
Shadowing can change type
= ;
= x.parse::<>().();
FAQ
Why would someone write let fields = fields; in Rust?
Usually to create a new binding with the same value, often changing it from mutable to immutable after a preparation step.
Does let x = x; copy the value?
Not always. It depends on the type.
Copytypes are copied.- Non-
Copytypes are moved.
Either way, the result is a new binding.
Is let x = x; considered idiomatic Rust?
Yes, in the right context. It is especially common when shadowing after parsing, transforming, or temporarily mutating a value.
Is this the same as x = x;?
No.
let x = x;creates a new binding.x = x;would be assignment to an existing mutable variable.
Can shadowing change a variable from mutable to immutable?
Yes.
let mut x = vec![1, 2, 3];
x.();
= x;
Mini Project
Description
Build a small Rust program that creates a list of numbers, sorts it, and then uses shadowing to make the sorted list immutable. This demonstrates the exact idea behind let fields = fields; in a practical, easy-to-run example.
Goal
Create, mutate, and then rebind a vector so that it can no longer be modified after setup.
Requirements
Create a mutable vector with unsorted numbers.
Sort the vector in place.
Shadow the vector with let numbers = numbers;.
Print the final sorted vector.
Show that further mutation would not be allowed.
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.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Can a Struct Extend Another Struct in Rust? Composition vs Inheritance
Learn how Rust handles struct reuse without inheritance, using composition, traits, and wrapper structs with practical examples.