ruchy 4.1.1

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
# RefCell Borrow Rules and Panic Prevention Guide

## Executive Summary

RefCell provides **runtime-checked** borrowing. Unlike Rust's compile-time borrow checker, violations cause **panics at runtime**. This guide ensures we NEVER panic in the Ruchy interpreter.

## Core Borrow Rules

### Rule 1: Multiple Immutable Borrows Allowed
```rust
let obj = Rc::new(RefCell::new(hashmap));

// ✅ SAFE: Multiple simultaneous reads
let borrow1 = obj.borrow();
let borrow2 = obj.borrow();
let val1 = borrow1.get("key1");
let val2 = borrow2.get("key2");
// Both borrows alive simultaneously - OK!
```

### Rule 2: Only ONE Mutable Borrow at a Time
```rust
// ✅ SAFE: Single mutable borrow
{
    let mut borrow = obj.borrow_mut();
    borrow.insert("key", value);
} // Borrow dropped here

// ❌ PANIC: Two mutable borrows
let mut borrow1 = obj.borrow_mut();
let mut borrow2 = obj.borrow_mut(); // PANIC!
```

### Rule 3: No Immutable Borrow While Mutable Exists
```rust
// ❌ PANIC: Read while writing
let mut write_borrow = obj.borrow_mut();
let read_borrow = obj.borrow(); // PANIC!

// ✅ SAFE: Release write before read
{
    let mut write_borrow = obj.borrow_mut();
    write_borrow.insert("key", value);
} // Write borrow dropped
let read_borrow = obj.borrow(); // OK!
```

## Panic Scenarios in Ruchy

### Scenario 1: Nested Field Access During Mutation

**UNSAFE CODE:**
```rust
fn eval_assign_nested_bad(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    let borrowed = obj.borrow(); // Immutable borrow
    let key = borrowed.get("key"); // Still borrowed!

    obj.borrow_mut().insert("other", value); // PANIC! Already borrowed
}
```

**SAFE CODE:**
```rust
fn eval_assign_nested_safe(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    // Clone the value while borrowed, then release
    let key = obj.borrow().get("key").cloned();
    // Borrow released here

    obj.borrow_mut().insert("other", value); // OK!
}
```

**Why this works:** `.cloned()` creates an owned value, allowing the borrow to drop before the next borrow.

### Scenario 2: Method Call Accessing Self Fields

**UNSAFE CODE:**
```rust
fn eval_method_call_bad(instance: &Rc<RefCell<HashMap<String, Value>>>) {
    let borrowed = instance.borrow();
    let method = borrowed.get("__method").cloned().unwrap();

    // Still borrowed!
    if let Value::Closure { body, .. } = method {
        // Closure execution might try to borrow_mut instance
        self.eval_expr(&body); // PANIC if body contains self.field = value
    }
}
```

**SAFE CODE:**
```rust
fn eval_method_call_safe(instance: &Rc<RefCell<HashMap<String, Value>>>) {
    // Clone method THEN release borrow
    let method = instance.borrow().get("__method").cloned().unwrap();
    // Borrow released here

    if let Value::Closure { body, .. } = method {
        // Now safe to execute, can borrow_mut if needed
        self.eval_expr(&body); // OK!
    }
}
```

**Pattern:** Always `.cloned()` values from RefCell, never hold borrow across other operations.

### Scenario 3: Actor Message Handler Execution

**UNSAFE CODE:**
```rust
fn process_message_bad(actor: &Rc<RefCell<HashMap<String, Value>>>, msg: &Value) {
    let handlers = actor.borrow().get("__handlers").cloned().unwrap();
    let state_ref = &actor.borrow(); // Immutable borrow held

    // Execute handler which might mutate state
    execute_handler(handler, state_ref); // PANIC if handler does self.field = x
}
```

**SAFE CODE:**
```rust
fn process_message_safe(actor: &Rc<RefCell<HashMap<String, Value>>>, msg: &Value) {
    // Get handler, release borrow
    let handler = actor.borrow().get("__handlers").cloned().unwrap();
    // Borrow released here

    // Execute handler with actor reference (can borrow_mut internally)
    execute_handler(handler, actor); // OK!
}
```

**Pattern:** Pass the `Rc<RefCell<>>` itself, not a borrow of it.

### Scenario 4: Iterating While Mutating

**UNSAFE CODE:**
```rust
fn iterate_and_mutate_bad(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    let borrowed = obj.borrow();
    for (key, value) in borrowed.iter() {
        // Still borrowed!
        if condition(value) {
            obj.borrow_mut().remove(key); // PANIC! Already borrowed
        }
    }
}
```

**SAFE CODE:**
```rust
fn iterate_and_mutate_safe(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    // Collect keys first, release borrow
    let keys_to_remove: Vec<String> = obj.borrow()
        .iter()
        .filter(|(_, v)| condition(v))
        .map(|(k, _)| k.clone())
        .collect();
    // Borrow released here

    // Now mutate
    let mut borrowed_mut = obj.borrow_mut();
    for key in keys_to_remove {
        borrowed_mut.remove(&key); // OK!
    }
}
```

**Pattern:** Separate read and write phases. Collect data during read, mutate during write.

## Safe Patterns for Ruchy Interpreter

### Pattern 1: Clone-Release-Mutate

```rust
// ✅ ALWAYS SAFE
fn safe_pattern_1(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    let value = obj.borrow().get("key").cloned(); // Clone and release
    obj.borrow_mut().insert("other", new_value);  // OK!
}
```

### Pattern 2: Scoped Borrows

```rust
// ✅ ALWAYS SAFE
fn safe_pattern_2(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    {
        let borrowed = obj.borrow();
        process(borrowed.get("key"));
    } // Borrow explicitly dropped

    obj.borrow_mut().insert("key", value); // OK!
}
```

### Pattern 3: Early Drop

```rust
// ✅ ALWAYS SAFE
fn safe_pattern_3(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    let borrowed = obj.borrow();
    let value = borrowed.get("key").cloned();
    drop(borrowed); // Explicitly drop borrow

    obj.borrow_mut().insert("other", value); // OK!
}
```

### Pattern 4: Separate Functions

```rust
// ✅ ALWAYS SAFE
fn read_field(obj: &Rc<RefCell<HashMap<String, Value>>>) -> Option<Value> {
    obj.borrow().get("key").cloned()
} // Borrow released on function return

fn write_field(obj: &Rc<RefCell<HashMap<String, Value>>>, val: Value) {
    obj.borrow_mut().insert("key", val); // OK, separate function
}

fn process(obj: &Rc<RefCell<HashMap<String, Value>>>) {
    let val = read_field(obj);  // Borrow released
    write_field(obj, new_val);  // OK!
}
```

**Pattern:** Separate read and write into different functions. Borrows released at function boundaries.

## Defensive Programming with try_borrow

For production code where we want to catch errors gracefully:

```rust
fn defensive_field_access(obj: &Rc<RefCell<HashMap<String, Value>>>) -> Result<Value, InterpreterError> {
    obj.try_borrow()
        .map_err(|_| InterpreterError::RuntimeError("Borrow check failed".to_string()))?
        .get("field")
        .cloned()
        .ok_or_else(|| InterpreterError::RuntimeError("Field not found".to_string()))
}
```

**When to use:**
- User-facing operations that shouldn't crash
- External API boundaries
- Debugging mode

**When NOT to use:**
- Internal interpreter operations (panic = bug, should fix)
- Performance-critical paths (adds overhead)

## Why Ruchy Won't Panic

**Single-threaded interpreter guarantees:**

1. **No concurrent access**: Only one thread executing interpreter code
2. **Sequential operations**: Each operation completes before next starts
3. **Scoped borrows**: Borrows released before next operation
4. **No recursion holding borrows**: Functions release borrows before recursion

**Example Safe Execution:**
```
1. eval_expr(actor ! message)
   ├─ Get actor instance: actor.borrow().get("__handlers").cloned() [RELEASED]
   ├─ Execute handler body: eval_expr(handler.body)
   │  └─ Field assignment: actor.borrow_mut().insert("field", value) [OK]
   └─ Return result
```

Each step releases borrows before the next step.

## Testing Strategy

### Unit Tests for Borrow Safety

```rust
#[test]
fn test_no_panic_sequential_access() {
    let obj = Rc::new(RefCell::new(HashMap::new()));

    // Sequential reads - should not panic
    for _ in 0..1000 {
        let _ = obj.borrow().get("key");
    }
}

#[test]
fn test_no_panic_sequential_writes() {
    let obj = Rc::new(RefCell::new(HashMap::new()));

    // Sequential writes - should not panic
    for i in 0..1000 {
        obj.borrow_mut().insert("key", Value::Integer(i));
    }
}

#[test]
fn test_no_panic_read_write_alternating() {
    let obj = Rc::new(RefCell::new(HashMap::new()));

    // Alternating read/write - should not panic
    for i in 0..1000 {
        obj.borrow_mut().insert("key", Value::Integer(i));
        let _ = obj.borrow().get("key").cloned();
    }
}

#[test]
#[should_panic]
fn test_panic_nested_borrow() {
    let obj = Rc::new(RefCell::new(HashMap::new()));

    let _borrow = obj.borrow(); // Hold borrow
    let _mut_borrow = obj.borrow_mut(); // PANIC!
}
```

### Property Tests

Already created in Phase 2:
- `prop_refcell_never_panics_on_borrow`: Verifies 10,000 operations don't panic

## Code Review Checklist

When reviewing code with RefCell:

- [ ] All `.borrow()` calls are followed by `.cloned()` or scoped
- [ ] No borrows held across function calls that might borrow
- [ ] Iterators don't hold borrows while mutating
- [ ] Methods on ObjectMut pass `Rc<RefCell<>>`, not `&RefCell<>`
- [ ] No borrows held across `eval_expr` calls
- [ ] Separate read and write phases clearly
- [ ] Comment any complex borrow patterns

## Summary

**Golden Rule:** Never hold a borrow across another borrow operation.

**Safe patterns:**
1. Clone then release: `.borrow().get().cloned()`
2. Scoped borrows: `{ let b = obj.borrow(); ... }`
3. Separate functions: Read in one function, write in another

**Unsafe patterns to avoid:**
1. Nested borrows: Holding borrow while trying another
2. Iterating while mutating: `for` loop with borrow_mut inside
3. Passing borrowed refs: Pass `&Rc<RefCell<>>` not `&HashMap<>`

Following these rules, Ruchy's RefCell usage will be **panic-free**.