Question

What `let fields = fields;` Does in Rust

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().min().unwrap());
let fields = fields;

The call to sort_by_key requires fields to be mutable, because sorting changes the collection in place.

A common pattern is:

let mut fields = ...;
fields.sort_by_key(...);
let fields = fields;

After sorting, the developer may no longer want fields to be mutable. Writing let fields = fields; creates a new immutable binding with the same value.

So the line is often used to say:

  • “I needed this variable to be mutable for a moment”
  • “Now I want to keep using it as immutable”

That improves clarity and can prevent accidental mutation later.

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 fields to it.
  • From that point on, when you say fields, you mean the new box, not the old one.

For let fields = fields;, imagine this:

  1. You have a temporary workbench copy labeled fields that you are allowed to rearrange.
  2. Once you finish rearranging it, you put the result into a clean final slot, also labeled fields.
  3. 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.

Take Quiz

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);
}

What this means

  • numbers is mutable at first so it can be sorted.
  • After sorting, let numbers = numbers; creates a new immutable binding.
  • From then on, you cannot accidentally modify numbers.

Another shadowing example with transformation

fn main() {
    let input = "42";
    let input = input.parse::<i32>().unwrap();

    println!("{}", input + 1);
}

Here shadowing is used to keep the same name while changing the value from &str to i32.

Important: this is not normal assignment

Rust assignment looks like this:

let mut x = 1;
x = 2;

Shadowing looks like this:

let x = 1;
let x = 2;

Assignment changes an existing mutable variable. Shadowing creates a new variable binding.

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 fields is 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]
  • This works because the binding is mutable.

3. Shadow it with a new immutable binding

let fields = fields;
  • A new binding named fields is created.
  • It takes the value from the old fields.
  • The old binding is now shadowed.
  • The new binding is immutable because it does not use mut.

4. Use the new binding

println!("{:?}", fields);
  • This refers to the new immutable fields.
  • Output:
[1, 2, 3]

What if you try to mutate afterward?

fn main() {
    let mut fields = vec![3, 1, 2];
    fields.sort();
    let fields = fields;

    fields.push(4);
}

This will fail because the new fields is immutable.

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::<u32>().unwrap();

This avoids inventing names like data2 or parsed_data when the older versions are no longer needed.

4. Narrowing scope of an older binding

Shadowing lets developers move from one stage of processing to another while keeping code readable.

For example:

  • raw input
  • cleaned input
  • validated input
  • parsed input

Each stage can reuse the same name if the earlier form is no longer useful.

Take Quiz

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() { return Err("empty input"); } else { input };
let input = input.parse::<u64>()?;

This is common in CLI tools, web handlers, and configuration loading.

Guarding against accidental mutation

Even when Rust already enforces safety, developers still use immutability to make intent explicit.

A shadowed immutable binding can make maintenance easier because later readers know the value should not change.

Why not always just keep it mutable?

Because mutable state is harder to reason about. Limiting mutability to the smallest possible region makes code easier to understand and less error-prone.

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() {
    let mut x = 1;
    let x = x;
    x = 2;
}

This is broken because the new x is immutable.

To keep mutability, you must write:

let mut x = 1;
let mut x = x;
x = 2;

4. Overusing shadowing so code becomes hard to follow

This is legal but can get confusing:

let data = read();
let data = clean(data);
let data = transform(data);
let data = validate(data);

This can be fine in short pipelines, but in long sections it may hide what type or state data currently has.

5. Forgetting that moves may happen

For owned values, the old binding may be moved into the new one:

let s = String::from("hello");
let s = s;

This is fine because the old s is immediately shadowed, but it still matters conceptually: ownership has moved into the new binding.

Comparisons

ConceptSyntaxCreates new binding?Requires mut?Can change type?
Shadowinglet x = x; or let x = expr;YesNoYes
Assignmentx = expr;NoYesNo

Shadowing vs assignment

let mut x = 10;
x = 20;
  • updates the same variable
  • needs mut
  • keeps the same type
let x = 10;
let x = 20;
  • creates a new binding
  • does not require the old one to be mutable
  • may reuse the same name

Shadowing vs using a different name

let mut fields = get_fields();
fields.sort();
let sorted_fields = fields;

This is also valid.

Trade-off

  • let fields = fields; keeps the name short and consistent.
  • let sorted_fields = fields; may be clearer if the new meaning is important.

Shadowing vs inner scopes

let x = 1;
{
    let x = 2;
    println!("{}", x);
}
println!("{}", x);

Inner scopes also allow shadowing, but the outer binding becomes visible again after the block ends. With sequential shadowing, the older binding stays hidden for the rest of the scope.

Take Quiz

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

let x = "42";
let x = x.parse::<i32>().unwrap();

Rules to remember

  • let always creates a new binding.
  • mut does not carry over unless you write it again.
  • Shadowing is often used to limit mutability.
  • Reusing the same name is idiomatic in Rust when earlier versions are no longer needed.

In the original example

fields.sort_by_key(...);
let fields = fields;

Most likely meaning:

  • fields needed to be mutable for sorting
  • after sorting, the author wanted an immutable fields

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.

  • Copy types are copied.
  • Non-Copy types 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.sort();
let x = x;

Now x is immutable.

Can shadowing change the type of a variable?

Yes.

let x = "42";
let x = x.parse::<i32>().unwrap();

The first x is &str; the second is i32.

Why not just keep using a mutable variable?

Because limiting mutability makes code easier to reason about and helps prevent accidental changes later.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more