DEV Community

artydev
artydev

Posted on

Understanding Rust's Borrowing Concept: A JavaScript Problem Story

The Problem: Multiple References Gone Wrong

Let me show you a common problem that JavaScript developers face every dayβ€”but don't always realize it's a problem.

The JavaScript Scenario

Imagine you have an array, and you create multiple references to it:

const x = [1, 2, 3]
const y = x  // another reference to same array
const z = x  // and another

function sleep(ms) {
  return new Promise((res) => setTimeout(res, ms))
}

function* geny() {
  let i = 0
  while (1) {
    yield y[i]  // iterating through y
    i = (i + 1) % 3
  }
}

const iterY = geny()

async function displayData() {
  while (1) {
    await sleep(1000)
    console.log(iterY.next())
  }
}

// After 5 seconds, mutate the array through z
setTimeout(() => { z[0] = 90 }, 5000)
displayData()
Enter fullscreen mode Exit fullscreen mode

The Output

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 1, done: false }
{ value: 2, done: false }
// ... 5 seconds pass ...
{ value: 90, done: false }  // 😱 SURPRISE! The value changed!
{ value: 2, done: false }
{ value: 3, done: false }
Enter fullscreen mode Exit fullscreen mode

What Went Wrong?

The problem is subtle but dangerous:

  1. Multiple references to the same data (x, y, z all point to [1, 2, 3])
  2. Mutation through one reference (z[0] = 90) silently affects all others
  3. No way to track where the mutation came from
  4. Hard to reason about β€” the code iterating through y suddenly sees different values

This is called aliasing and mutation β€” a notoriously difficult bug to track down in large codebases.


The Rust Solution: Borrowing

Rust prevents this problem entirely by enforcing strict borrowing rules at compile time. Let's see how:

Rust's Approach

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

    // This creates an immutable borrow
    let y = &x;

    // ❌ ERROR: Cannot borrow as mutable while y exists!
    // let z = &mut x;
    // z[0] = 90;

    println!("{:?}", y);  // y is still in use here
}
Enter fullscreen mode Exit fullscreen mode

The compiler says: NO! βœ‹

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
Enter fullscreen mode Exit fullscreen mode

The Borrowing Rules (Rust's Safety Guarantee)

Rust enforces these rules:

Scenario Allowed? Why?
Multiple immutable borrows (&x, &x, &x) βœ… YES Readers never conflict
One mutable borrow (&mut x) βœ… YES Single writer, no conflicts
Immutable + Mutable (&x and &mut x) ❌ NO Writer and readers = data race

Correct Rust Code

If you want to mutate the data, you must release all other references first:

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

    // Immutable borrow
    let y = &x;
    println!("y: {:?}", y);

    // y is no longer used, borrow ends here

    // Now we can create a mutable borrow
    let z = &mut x;
    z[0] = 90;

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

Output:

y: [1, 2, 3]
z: [90, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Comparing the Languages

JavaScript: Trust and Hope πŸ™

const x = [1, 2, 3]
const y = x
const z = x

z[0] = 90

console.log(y)  // [90, 2, 3] ← Surprise! You have to remember to check
Enter fullscreen mode Exit fullscreen mode

Relies on:

  • Developer discipline
  • Code reviews
  • Runtime testing
  • Hope that you didn't miss anything πŸ˜…

Rust: Compile-Time Guarantees πŸ¦€

let mut x = vec![1, 2, 3];
let y = &x;
let z = &mut x;  // ❌ Compiler error BEFORE runtime!
z[0] = 90;
Enter fullscreen mode Exit fullscreen mode

Guarantees:

  • Checked at compile time
  • No runtime surprises
  • Clear ownership semantics
  • Safe by default

Why This Matters

JavaScript Problems (Real-World Scenarios)

  1. Shared state bugs β€” State changes in unexpected places
  2. Iterator invalidation β€” Data changes while iterating
  3. Race conditions β€” Concurrent modifications
  4. Difficult debugging β€” Hard to trace where mutation happened

Rust Solutions

// Rust FORCES you to be explicit about access patterns

// Read-only access (shared)
let reader1 = &data;
let reader2 = &data;
let reader3 = &data;
// All readers are safe!

// Exclusive access (mutation)
let writer = &mut data;
// Only one writer, guaranteed no conflicts
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

βœ… JavaScript allows multiple mutable references β€” flexible but error-prone

βœ… Rust prevents multiple mutable references β€” strict but safe

βœ… The JavaScript code showed a *real problem* that Rust's borrowing solves

βœ… Borrowing is not a limitation β€” it's a feature that catches bugs at compile time

βœ… Trade-off: Less flexibility for more safety and predictability


The Bigger Picture

This is why Rust is gaining traction in systems programming:

  • Memory safety without garbage collection
  • No runtime overhead for safety checks
  • Concurrency without fear (data races are impossible)
  • Performance + Reliability together

While JavaScript's flexibility is great for rapid development, Rust's borrowing system is invaluable for:

  • Large codebases
  • Concurrent systems
  • Performance-critical code
  • Safety-critical applications

Try It Yourself

Want to experiment?

JavaScript: Run the code in your browser console and watch the mutation happen silently. TryTheSample

Rust: Head to play.rust-lang.org and try violating the borrowing rulesβ€”the compiler will teach you! πŸ¦€

fn main() {
    let mut data = vec![1, 2, 3];
    let immutable_ref = &data;
    let mutable_ref = &mut data;  // Try this!
    mutable_ref[0] = 99;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Rust's borrowing system might seem restrictive at first, but it solves real problems that JavaScript developers face daily. By catching aliasing-and-mutation bugs at compile time, Rust gives you confidence that your code is correct before it ever runs.

The JavaScript example above isn't a flaw in JavaScriptβ€”it's a feature. The flaw is when that feature leads to subtle bugs. Rust simply chose a different trade-off: less flexibility, zero surprises.

Happy coding! πŸ¦€βœ¨


Have you encountered aliasing-and-mutation bugs in your code? Share your war stories in the comments! And if you're curious about Rust, the borrowing rules might just be the best investment you make in understanding systems programming.

Top comments (0)