DEV Community

Cover image for JavaScript References vs Rust Ownership
artydev
artydev

Posted on

JavaScript References vs Rust Ownership

JavaScript References vs Rust Ownership

JavaScript variables that hold arrays or objects store references (pointers to the data). When you assign one variable to another, you copy the reference, not the actual data itself. This means multiple variables can point to the same object.

JavaScript Example

let x = [1, 2, 3];
let y = x;
let z = x;

y[0] = 90;

console.log(x); // [90, 2, 3]
console.log(y); // [90, 2, 3]
console.log(z); // [90, 2, 3]
Enter fullscreen mode Exit fullscreen mode

What's happening in memory:

All three variables (x, y, z) point to the same array:

Initial state:

After y[0] = 90:

The Problem: All three variables point to the identical array in memory. When you modify the array through one variable, every other variable that points to it sees the change. This silent sharing can cause unexpected bugs.


How Rust Prevents This

Rust uses an ownership and borrowing system to prevent accidental shared changes to data. The compiler checks your code and prevents dangerous patterns before the program even runs.

❌ Not Allowed: Multiple Variables Can Change the Same Data

let mut x = vec![1, 2, 3];
let y = &mut x;  // y can modify x
let z = &mut x;  // ERROR: z cannot also modify x!
Enter fullscreen mode Exit fullscreen mode

Rust compiler error:

cannot borrow x as mutable more than once at a time
Enter fullscreen mode Exit fullscreen mode

Rust refuses to compile this because it prevents the exact problem we saw in JavaScript.


✅ Allowed: Multiple Variables Can Read the Same Data

let x = vec![1, 2, 3];
let y = &x;  // y can read x (but not change it)
let z = &x;  // z can also read x (but not change it)

println!("{:?}", y);
println!("{:?}", z);
Enter fullscreen mode Exit fullscreen mode

Why this is safe: Since nobody can modify the data, it's safe for many variables to read it at once. No unexpected changes can happen.


✅ Allowed: One Variable Can Change the Data

let mut x = vec![1, 2, 3];

{
  let y = &mut x;  // y can modify x
  y[0] = 90;
}  // y goes out of scope here

println!("{:?}", x);  // x is now [90, 2, 3]
Enter fullscreen mode Exit fullscreen mode

How it works: The variable y has exclusive access to modify x. Once y goes out of scope (exits the block), x is available again. There's no shared mutable state.

You Can Create a New Mutable Variable After the First One Ends

let mut x = vec![1, 2, 3];

{
  let y = &mut x;  // y can modify x
  y[0] = 90;
}  // y's access ends here

{
  let z = &mut x;  // NOW z can modify x (y is gone)
  z[1] = 50;
}  // z's access ends here

println!("{:?}", x);  // x is now [90, 50, 3]
Enter fullscreen mode Exit fullscreen mode

Why this is safe: Each variable (y, then z) gets exclusive access to x for its turn. They never overlap. Once y exits its scope, z can safely take over. This prevents simultaneous mutations but allows sequential access.

Without the inner scopes (also allowed):

let mut x = vec![1, 2, 3];

let y = &mut x;
y[0] = 90;
// y is no longer used after this point

let z = &mut x;  // z can now borrow x (y isn't used anymore)
z[1] = 50;

println!("{:?}", x);  // x is now [90, 50, 3]
Enter fullscreen mode Exit fullscreen mode

How this works: Rust is smart enough to know when a variable is no longer being used. Even though y is still "in scope," once you stop using it, z can take over and get mutable access. This is called "non-lexical lifetimes" or NLL—the compiler looks at actual usage, not just scope boundaries.


Rust's Borrowing Rules (Simple Version)

What You're Trying to Do Is It Allowed? Why?
Many variables reading (immutable borrows) ✅ Yes Safe. Nothing can change the data.
One variable changing (mutable borrow) ✅ Yes Safe. Only one writer, no readers.
Multiple variables changing at once ❌ No Dangerous. Could cause unpredictable bugs.
Some reading and one changing at the same time ❌ No Dangerous. Readers might see incomplete changes.

When You Really Need Shared Ownership in Rust

If your program actually needs multiple owners (like passing data to multiple parts of your code), Rust makes you use special containers that make this explicit and safe.

Simple shared ownership (single-threaded):

use std::rc::Rc;

let x = Rc::new(vec![1, 2, 3]);
let y = Rc::clone(&x);  // y also owns the vector
let z = Rc::clone(&x);  // z also owns the vector
// All three own it, but only one can modify it
Enter fullscreen mode Exit fullscreen mode

Shared ownership that can be modified (single-threaded):

Rc<RefCell<T>>
Enter fullscreen mode Exit fullscreen mode

Translation: A shared container that allows interior mutation (changing the contents through shared references).

Shared ownership that can be modified (multi-threaded):

Arc<Mutex<T>>
Enter fullscreen mode Exit fullscreen mode

Translation: A thread-safe shared container with a mutual exclusion lock (only one thread at a time can modify).

The point: When Rust allows shared mutable state, it's explicit and safe. You must opt-in with special types that prevent data races.


Side-by-Side Comparison

Feature JavaScript Rust
How variables store objects As references (pointers) Through ownership rules
Can multiple variables modify the same data? Yes, silently and automatically No, only one at a time
When are problems caught? At runtime (if you're lucky) At compile time (always)
Can you share data intentionally? Yes, by default (sometimes accidentally) Yes, but you must use explicit types (Rc, Arc, etc.)
Shared mutations Happen without any warnings Require explicit safe containers (RefCell, Mutex)

The Bottom Line

JavaScript: Multiple variables can freely reference and modify the same object. This flexibility is convenient but can hide bugs.

Rust: The compiler enforces strict ownership rules. Only one variable can modify data at a time, and this is checked before your program runs. You get safety without sacrificing performance.

In short: JavaScript gives you freedom but less safety. Rust gives you safety but requires you to be explicit about how data is shared. Both approaches have trade-offs, but Rust prevents entire categories of memory-related bugs that JavaScript developers have to debug manually.

Top comments (0)