Module C2_Variables_and_Scopes

Module C2_Variables_and_Scopes 

Source
Expand description

§🧩 Variables, Let Bindings, and Scoping

Variables in Compose are introduced using the let keyword. Every variable has a scopeβ€”the region of code where it is visibleβ€”and Compose is designed to make variable behavior clear and predictable.

Β§πŸ“Œ Let Bindings

To bind a value to a name, use let:

let x = 10;
let name = "Compose";

By default, variables are immutableβ€”once assigned, they cannot be changed:

let x = 5;
x = 6;
error[E0004]: cannot reassign to a variable declared as immutable
  β”Œβ”€ main.comp:2:1
  β”‚
1 β”‚ let x = 5;
  β”‚     - was defined as immutable here
2 β”‚ x = 6;
  β”‚ ^ is immutable
  β”‚
  = note: variables are immutable by default
  = help: make the variable mutable by writing `let mut`
  = help: for more information about this error, try `compose explain E0004`

If you want to change the value later, use let mut to make the variable mutable:

let mut count = 0;
count = count + 1;
assert::eq(count, 1);

Compose encourages immutability for clarity, but allows mutation when it’s explicitly requested.


§🚫 Uninitialized Bindings

In Compose, you can declare a variable without giving it an initial value.

This can be useful when multiple branches assign different values to the same variable:

let x;
if (true) {
    x = 1;
} else {
    x = 2;
};
assert::eq(x, 1);

However, in this instance we might use the fact that if is an expression to assign the intended value to x directly.

let x = if (true) { 1; } else { 2; };
assert::eq(x, 1);

Reading an uninitialized variable is allowed and resolves to () (the unit value). However, this produces a warning and relying on this behaviour should be avoided.

let x;
assert::eq(x, ());
warning[W0001]: use of variable before it has been initialized
  β”Œβ”€ main.comp:2:12
  β”‚
1 β”‚ let x;
  β”‚     - variable declared here without an initial value
2 β”‚ assert::eq(x, ());
  β”‚            ^ use of uninitialized variable
  β”‚
  = note: uninitialised variables evaluate to `()` by default
  = help: for more information about this error, try `compose explain W0001`

Β§πŸ“š Variable Scope

The scope of a variable starts where it is declared and ends at the closing brace of the block that contains it.

let outer = 10;
{
    let inner = 20;
    assert::eq(outer, 10);
    assert::eq(inner, 20);
};
assert::eq(inner, 10); // inner is out of scope here
error[E0011]: Unbound variable: `inner`
  β”Œβ”€ main.comp:7:12
  β”‚
7 β”‚ assert::eq(inner, 10); // inner is out of scope here
  β”‚            ^^^^^ this variable is unbound here
  β”‚
  = help: for more information about this error, try `compose explain E0011`

Variables can shadow earlier bindings in a new scope:

let value = 1;
{
    let value = value + 1;
    assert::eq(value, 2);
};
assert::eq(value, 1);

Variables can also be shadowed in the same scope:

let x = 1;
let x = x + 1; // shadowing, not mutation
assert::eq(x, 2);

Shadowing is a way to β€œreuse” names safely. Each binding is separate and lives in its own scope.


Β§βœ… Summary

  • Variables are immutable by default. Use let mut to allow mutation.
  • Use let to bind values or patterns.
  • Variables can be uninitialised and resolve to () before assignment.
  • Scoping rules help prevent accidental usage of invalid or stale values.
  • Shadowing lets you reuse names in a controlled and explicit way.