Variables & Mutability
Variables are fundamental to any program. Fe provides a straightforward yet powerful system for declaring and working with variables, with immutability as the default for safer code.
Variable Declaration
Section titled “Variable Declaration”Use the let keyword to declare a variable:
let x: u256 = 42let name = "Alice"let is_active = trueWith Type Annotations
Section titled “With Type Annotations”You can explicitly specify a variable’s type using a colon followed by the type:
let x: u256 = 42let name: String<5> = "Alice"let balance: u128 = 1000Type annotations are optional when the compiler can infer the type from the value. However, explicit annotations are useful when:
- You need a specific integer size (e.g.,
u256vs inferred type) - The type isn’t obvious from context
- You want to document the intended type for clarity
// With annotation - type must be specified for integerslet count: u256 = 100
// Explicitly u8 for storage efficiencylet small_count: u8 = 100Uninitialized Variables
Section titled “Uninitialized Variables”You can declare a variable without immediately initializing it:
let mut x: u256// ... later in the codex = 42However, Fe will ensure the variable is initialized before it’s used.
Immutability by Default
Section titled “Immutability by Default”In Fe, variables are immutable by default. Once a value is bound to a name, it cannot be changed:
let x = 5x = 10 // Error: cannot assign to immutable variableThis design choice promotes:
- Safety: Prevents accidental modifications
- Predictability: Values don’t change unexpectedly
- Clarity: When you see a variable, you know its value won’t change
Mutable Variables
Section titled “Mutable Variables”When you need a variable whose value can change, use the mut keyword:
let mut counter: u256 = 0counter = 1 // OK: counter is mutablecounter = 2 // OK: can reassign multiple timesThe mut keyword signals to readers that this variable’s value will change during execution.
When to Use Mutability
Section titled “When to Use Mutability”Use mut when you genuinely need to modify a value:
let mut total = 0for item in items { total = total + item.value}Prefer immutable variables when possible. If you find yourself using mut frequently, consider whether there’s a more functional approach.
Variable Shadowing
Section titled “Variable Shadowing”Fe allows you to declare a new variable with the same name as a previous one. The new declaration shadows the previous one:
let x: u256 = 5let x: u256 = x + 1 // x is now 6, shadows the previous xlet x: u256 = x * 2 // x is now 12, shadows againShadowing is different from mutation:
- Shadowing creates a new variable (can even change the type)
- Mutation modifies an existing variable’s value
// Shadowing allows type changeslet value = "42" // value is a String<2>let value: u256 = 42 // value is now u256 (new variable)
// Mutation requires same typelet mut count: u256 = 0count = 1 // OK: same type// count = "one" // Error: type mismatchWhen to Use Shadowing
Section titled “When to Use Shadowing”Shadowing is useful when:
- Transforming a value through multiple steps
- Converting between types while keeping a meaningful name
- Reusing a name in a new scope without
mut
// Transform through stepslet input = get_raw_input()let input = validate(input)let input = process(input)Scope and Blocks
Section titled “Scope and Blocks”Variables are scoped to the block in which they’re declared. A block is code enclosed in curly braces {}:
let outer: u256 = 1
{ let inner: u256 = 2 // Both outer and inner are accessible here}
// Only outer is accessible here// inner is out of scopeNested Scopes
Section titled “Nested Scopes”Inner scopes can access variables from outer scopes:
let x: u256 = 10
{ let y: u256 = 20 let sum: u256 = x + y // OK: x is accessible from outer scope}Shadowing in Inner Scopes
Section titled “Shadowing in Inner Scopes”You can shadow an outer variable within an inner scope:
let x: u256 = 5
{ let x: u256 = 10 // Shadows outer x within this block // x is 10 here}
// x is 5 here (outer x unchanged)Pattern Bindings
Section titled “Pattern Bindings”The let statement supports pattern matching, allowing you to destructure values:
Tuple Destructuring
Section titled “Tuple Destructuring”let point: (u256, u256) = (10, 20)let (x, y) = point // x = 10, y = 20You can ignore parts of a tuple with _:
let (x, _) = point // Only bind x, ignore yStruct Destructuring
Section titled “Struct Destructuring”struct Point { x: u256, y: u256,}
let point = Point { x: 10, y: 20 }let Point { x, y } = point // x = 10, y = 20You can also rename bindings:
let Point { x: horizontal, y: vertical } = point// horizontal = 10, vertical = 20Mutable Pattern Bindings
Section titled “Mutable Pattern Bindings”Use mut in patterns to make specific bindings mutable:
let (mut x, y): (u256, u256) = (1, 2)x = 10 // OK: x is mutable// y = 20 // Error: y is immutableIn struct patterns:
let Point { mut x, y } = pointx = 100 // OK: x is mutableSummary
Section titled “Summary”| Feature | Syntax | Description |
|---|---|---|
| Immutable variable | let x = value | Cannot be reassigned |
| Mutable variable | let mut x = value | Can be reassigned |
| Type annotation | let x: Type = value | Explicit type |
| Shadowing | let x = ... (again) | New variable, same name |
| Tuple destructuring | let (a, b) = tuple | Bind tuple elements |
| Struct destructuring | let S { field } = s | Bind struct fields |