Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Variables and Scoping

This chapter covers variable declaration, assignment, scoping rules, and variable management in Crush.

Variable Declaration

Basic Declaration

let x = 42;
let name = "Alice";
let active = true;

Explicit Mutability

Use mut to mark a variable as explicitly mutable (same behavior as let today, but signals intent):

let mut counter = 0;
counter = counter + 1;

With Type Hints

let age: Int = 30;
let score: Float = 95.5;
let message: String = "Hello";
let flag: Bool = true;

Assignment and Reassignment

Variables declared with let are mutable by default:

let counter = 0;
counter = 1;
counter = counter + 1;  // counter is now 2

Compound Assignment (Future Feature)

counter += 1;   // counter = counter + 1
counter -= 1;   // counter = counter - 1
counter *= 2;   // counter = counter * 2
counter /= 2;   // counter = counter / 2

Scoping Rules

Function Scope

Variables declared in a function are local to that function:

fn example() {
    let x = 10;  // Local to example()
    io.print(x);
}

fn main() {
    example();
    // io.print(x);  // Error: x not in scope
}

Block Scope

Variables declared in a block are local to that block:

fn main() {
    let x = 10;
    
    if true {
        let y = 20;  // Local to if block
        io.print(x);  // Can access outer x
        io.print(y);
    }
    
    io.print(x);  // OK
    // io.print(y);  // Error: y not in scope
}

Global Scope (Module Level)

Variables at module level are accessible throughout the module:

let GLOBAL_CONFIG = "production";

fn get_config() -> String {
    return GLOBAL_CONFIG;
}

fn main() {
    io.print(GLOBAL_CONFIG);
}

Variable Shadowing

Inner scopes can shadow outer variables:

fn main() {
    let x = 10;
    io.print(x);  // 10
    
    {
        let x = 20;  // Shadows outer x
        io.print(x);  // 20
    }
    
    io.print(x);  // 10 (outer x restored)
}

Export and Import

Exporting Variables

// capsule_a.crush
export result = calculate();
export config = {"mode": "production"};

Importing Variables

// capsule_b.crush
let data = import.var("capsule_a", "result");
let cfg = import.var("capsule_a", "config");

Constants (Future Feature)

const PI = 3.14159;
const MAX_SIZE = 100;

// PI = 3.14;  // Error: cannot reassign constant

Variable Lifetime

Variables live until their scope ends:

fn main() {
    let x = allocate_resource();
    // x is alive
    
    if condition {
        let y = another_resource();
        // Both x and y are alive
    }  // y is destroyed here
    
    // Only x is alive
}  // x is destroyed here

Best Practices

1. Declare Variables Close to Use

// Good
fn process() {
    let data = fetch_data();
    transform(data);
}

// Less ideal
fn process() {
    let data;
    // ... many lines ...
    data = fetch_data();
    transform(data);
}

2. Use Meaningful Names

// Good
let user_count = 100;
let temperature_celsius = 25.5;

// Avoid
let x = 100;
let temp = 25.5;

3. Minimize Scope

// Good: Narrow scope
if needs_processing {
    let temp_data = process();
    use(temp_data);
}

// Less ideal: Wider scope
let temp_data;
if needs_processing {
    temp_data = process();
    use(temp_data);
}

4. Initialize Variables

// Good
let counter = 0;

// Avoid (future: may require initialization)
// let counter;
// counter = 0;

Next Steps