Skip to Content
Variables and Mutability

Variables and Mutability

In this article, you will learn about variables and mutability in the Rust programming language — one of the most foundational concepts that sets Rust apart from other languages.


What Are Variables in Rust?

By default, all variables in Rust are immutable. This means once you bind a value to a variable name, that value cannot be changed.

This design choice is intentional. Rust encourages you to write code that is safe, predictable, and easy to reason about — especially in concurrent programs. Immutability by default reduces an entire class of bugs where a value gets unexpectedly changed in a different part of the program.

That said, Rust still gives you the option to make a variable mutable when you need to — you just have to be explicit about it.


Immutability by Default

Let’s see what happens when you try to reassign an immutable variable.

Open src/main.rs in your project and try this code:

src/main.rs
fn main() { let x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }

Save the file and run it with:

cargo run

You will see a compile-time error:

Compiling rust_app v0.1.0 (/home/username/desktop-app/rust_app) error[E0384]: cannot assign twice to immutable variable `x` --> src/main.rs:4:5 | 2 | let x = 5; | - first assignment to `x` 3 | println!("The value of x is: {x}"); 4 | x = 6; | ^^^^^ cannot assign twice to immutable variable | help: consider making this binding mutable | 2 | let mut x = 5; | +++ For more information about this error, try `rustc --explain E0384` error: could not compile `rust_app` (bin "rust_app") due to 1 previous error

Rust catches this at compile time, not at runtime. This is one of Rust’s greatest strengths — the compiler acts as a safety net that prevents you from making mistakes before the program ever runs.

The error clearly tells you that you tried to assign to x twice, and since x is immutable by default, Rust refuses to compile the program. It even suggests the fix: add the mut keyword.

This feature is especially valuable in large codebases. If you define a variable in one part of your code and another part accidentally tries to change it, Rust will stop you immediately.


Making a Variable Mutable

To allow a variable’s value to change, add the mut keyword after let:

src/main.rs
fn main() { let mut x = 21; println!("The value of x is: {x}"); x = 22; println!("The value of x is: {x}"); }

Run it:

cargo run

Output:

Compiling rust_app v0.1.0 (/home/username/desktop-app/rust_app) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s Running `target/debug/rust_app` The value of x is: 21 The value of x is: 22

By adding mut, you’re allowed to change the value bound to x from 21 to 22. Using mut also acts as a signal to other developers reading your code — it communicates that this variable’s value is intentionally designed to change.


Constants

Now that you understand variables, let’s look at constants. Both constants and immutable variables cannot be changed, but they are different in a few important ways:

Featurelet (immutable)const
Keywordletconst
Can use mutYesNo
Type annotation requiredNo (inferred)Yes (always)
Allowed scopeAnyAny
Value set atCompile time or runtimeCompile time only
Naming conventionsnake_caseSCREAMING_SNAKE_CASE

Constants are declared with the const keyword and must always have their type annotated. They cannot be used with mut. Constants are evaluated at compile time, not at runtime.

src/main.rs
fn main() { const THREE_HOURS_IN_SECONDS: i32 = 60 * 60 * 3; println!("The value of constant is: {THREE_HOURS_IN_SECONDS}"); }

The constant THREE_HOURS_IN_SECONDS follows Rust’s naming convention for constants — all uppercase with underscores separating words.

Constants Must Be Evaluated at Compile Time

Constants can only be set to a value that the compiler can compute at compile time. They cannot be set to the result of a function that runs at runtime.

This is not allowed — the function runs at runtime:

fn get_value() -> i32 { 42 } const X: i32 = get_value(); // ❌ not allowed — runtime function

But this is allowed — the function is marked const, so it is evaluated at compile time:

const fn get_value() -> i32 { 42 } const X: i32 = get_value(); // ✅ allowed — const function

Constants are valid for the entire duration of the program, within the scope in which they are declared. They are useful for values that are meaningful across your entire application, such as the maximum number of connections or a fixed timeout duration.


Shadowing

Rust has another concept called shadowing, which is different from mutability. You can declare a new variable with the same name as an existing variable using let again. The second variable shadows the first — it takes over that name for any code that follows.

When you shadow a variable:

  • The new variable replaces (shadows) the old one
  • Any further use of that name refers to the new variable
  • The original variable is effectively hidden (but still exists until its scope ends)

Here is an example:

src/main.rs
fn main() { let x = 5; let x = x + 1; // shadows the previous x { let x = 10; // shadows outer x ONLY inside this block println!("{}", x); // prints 10 } println!("{}", x); // prints 6 }

Output:

10 6

Inside the inner block, x is shadowed to 10. Once that block ends, the inner shadow disappears and x returns to 6.

Shadowing vs mut — Key Difference

The most important difference between shadowing and mut is that shadowing allows you to change the type of a variable, while mut does not.

With shadowing, you can reuse the same name even for a different type:

src/main.rs
fn main() { let spaces = " "; // spaces is a &str (string) let spaces = spaces.len(); // spaces is now a usize (number) }

This works because shadowing creates a new variable — it does not modify the original. You don’t need to invent a new name like spaces_str or spaces_count.

Compare this to using mut, which does not allow a type change:

src/main.rs
fn main() { let mut spaces = " "; // mutable string spaces = spaces.len(); // ❌ ERROR: type mismatch }

Rust expected a &str but got a usize, so this fails at compile time. With mut, you can only reassign a value of the same type.

Use mut when you want to update a value of the same type. Use shadowing when you want to transform a value into a different type or apply a transformation while keeping the same name.


Summary

ConceptKeywordCan change value?Can change type?
Immutable variableletNoNo
Mutable variablelet mutYesNo
ConstantconstNoNo
Shadowed variablelet (again)Yes (new binding)Yes

What’s Next?

Now that you understand how variables work in Rust, you’re ready to explore Data Types — where you’ll learn about Rust’s scalar and compound types, and how type annotations work in practice.

Last updated on