Variables and Mutability
One of the most noticeable differences between Rust and JavaScript is how variables and mutability work. Let’s explore these differences to help you transition between the two languages.
Variables in JavaScript
In JavaScript, you can declare variables with var
, let
, or const
:
// Variable declarations in JavaScriptvar oldSchool = "I'm hoisted";let mutable = "I can be changed";const immutable = "I cannot be reassigned";
// Reassignmentmutable = "New value"; // ✅ Works// immutable = "New value"; // ❌ TypeError: Assignment to constant variable
// But objects declared with const CAN be modifiedconst person = { name: "Alice" };person.name = "Bob"; // ✅ Works! Only the binding is immutable
Variables in Rust
Rust takes a different approach, making variables immutable by default:
// Variable declarations in Rustlet immutable = "I cannot be changed"; // Immutable by defaultlet mut mutable = "I can be changed"; // Use 'mut' to make mutable
// Reassignment// immutable = "New value"; // ❌ Error: cannot assign twice to immutable variablemutable = "New value"; // ✅ Works
Key Differences
- In JavaScript, variables declared with
let
are mutable by default - In Rust, variables are immutable by default and require
mut
for mutability - JavaScript’s
const
prevents reassignment but not modification of object properties - In Rust, immutability is more comprehensive (we’ll see more when we get to references)
Constants
Both languages have constants, but they work differently:
JavaScript Constants
const PI = 3.14159;// PI = 3.0; // ❌ Error: Assignment to constant variable
// But objects can be modifiedconst user = { name: "Alice" };user.name = "Bob"; // ✅ Works - only the binding is constant, not the content
Rust Constants
const PI: f64 = 3.14159;// PI = 3.0; // ❌ Error: cannot assign to this expression
// Constants require type annotations and use SCREAMING_SNAKE_CASE by conventionconst MAX_POINTS: u32 = 100_000;
Rust constants:
- Must have explicit type annotations
- Can only be set to a constant expression (evaluated at compile time)
- Can be declared in any scope, including the global scope
- Are inlined at compile time
Shadowing in Rust
Rust allows “shadowing” of variables, which is declaring a new variable with the same name as a previous variable:
let x = 5;let x = x + 1; // Shadows the previous 'x'let x = x * 2; // Shadows again
println!("The value of x is: {}", x); // 12
This is different from mutation:
// With shadowinglet spaces = " ";let spaces = spaces.len(); // ✅ Works, type can change with shadowing
// With mutationlet mut spaces = " ";// spaces = spaces.len(); // ❌ Error: mismatched types
JavaScript Variable Shadowing
JavaScript has similar behavior with block-scoped variables:
let x = 5;{ let x = x + 1; // Shadows the outer 'x' console.log(x); // 6}console.log(x); // 5
However, you can’t redeclare the same variable in the same scope in JavaScript:
let x = 10;let x = 20; // ❌ SyntaxError: Identifier 'x' has already been declared
Variable Scope
Both languages use curly braces to define scopes:
JavaScript
{ let x = 10; console.log(x); // 10}// console.log(x); // ❌ ReferenceError: x is not defined
Rust
{ let x = 10; println!("x: {}", x); // 10}// println!("x: {}", x); // ❌ Error: cannot find value `x` in this scope
Naming Conventions
JavaScript
camelCase
for variables and functionsPascalCase
for classes and constructor functionsSCREAMING_SNAKE_CASE
for constants
Rust
snake_case
for variables and functionsPascalCase
for types and traitsSCREAMING_SNAKE_CASE
for constants and static variables
Type Inference
Both Rust and JavaScript can infer types, but in very different ways:
JavaScript (Dynamic Typing)
let x = 5; // Numberx = "hello"; // Now it's a String - no problem!
Rust (Static Typing with Inference)
let x = 5; // Inferred as i32// x = "hello"; // ❌ Error: mismatched types
// Need explicit type annotation for type changeslet x = 5; // i32let x: &str = "hello"; // Shadowing with a new type
Destructuring
Both languages support destructuring:
JavaScript
const point = { x: 10, y: 20 };const { x, y } = point;console.log(x, y); // 10 20
const numbers = [1, 2, 3];const [first, second] = numbers;console.log(first, second); // 1 2
Rust
struct Point { x: i32, y: i32 }let point = Point { x: 10, y: 20 };let Point { x, y } = point;println!("{} {}", x, y); // 10 20
let numbers = [1, 2, 3];let [first, second, ..] = numbers;println!("{} {}", first, second); // 1 2
Best Practices
JavaScript
- Use
const
by default andlet
only when reassignment is needed - Avoid
var
in modern code - Consider using TypeScript for static typing
Rust
- Embrace immutability - use
let
withoutmut
by default - Use
mut
only when necessary - Take advantage of shadowing for transformations
- Let the compiler infer types when obvious
Memory Considerations
An important difference that isn’t immediately visible is how these variables are stored in memory:
JavaScript
JavaScript handles memory allocation and garbage collection automatically. You generally don’t need to worry about where values live.
Rust
In Rust, values can be allocated on the stack or heap, and the ownership system (which we’ll cover later) determines how memory is managed.
// Stack allocatedlet x = 5;
// Heap allocated, but Rust abstracts memory managementlet s = String::from("hello");
Next Steps
Now that you understand variables and mutability in Rust, let’s move on to Data Types to learn about Rust’s rich type system.