Skip to content
Pre-Release: Fe is under active development. This documentation covers the upcoming release. Follow progress on GitHub

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.

Use the let keyword to declare a variable:

let x: u256 = 42
let name = "Alice"
let is_active = true

You can explicitly specify a variable’s type using a colon followed by the type:

let x: u256 = 42
let name: String<5> = "Alice"
let balance: u128 = 1000

Type 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., u256 vs 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 integers
let count: u256 = 100
// Explicitly u8 for storage efficiency
let small_count: u8 = 100

You can declare a variable without immediately initializing it:

let mut x: u256
// ... later in the code
x = 42

However, Fe will ensure the variable is initialized before it’s used.

In Fe, variables are immutable by default. Once a value is bound to a name, it cannot be changed:

let x = 5
x = 10 // Error: cannot assign to immutable variable

This 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

When you need a variable whose value can change, use the mut keyword:

let mut counter: u256 = 0
counter = 1 // OK: counter is mutable
counter = 2 // OK: can reassign multiple times

The mut keyword signals to readers that this variable’s value will change during execution.

Use mut when you genuinely need to modify a value:

let mut total = 0
for 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.

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 = 5
let x: u256 = x + 1 // x is now 6, shadows the previous x
let x: u256 = x * 2 // x is now 12, shadows again

Shadowing is different from mutation:

  • Shadowing creates a new variable (can even change the type)
  • Mutation modifies an existing variable’s value
// Shadowing allows type changes
let value = "42" // value is a String<2>
let value: u256 = 42 // value is now u256 (new variable)
// Mutation requires same type
let mut count: u256 = 0
count = 1 // OK: same type
// count = "one" // Error: type mismatch

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 steps
let input = get_raw_input()
let input = validate(input)
let input = process(input)

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 scope

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
}

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)

The let statement supports pattern matching, allowing you to destructure values:

let point: (u256, u256) = (10, 20)
let (x, y) = point // x = 10, y = 20

You can ignore parts of a tuple with _:

let (x, _) = point // Only bind x, ignore y
struct Point {
x: u256,
y: u256,
}
let point = Point { x: 10, y: 20 }
let Point { x, y } = point // x = 10, y = 20

You can also rename bindings:

let Point { x: horizontal, y: vertical } = point
// horizontal = 10, vertical = 20

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 immutable

In struct patterns:

let Point { mut x, y } = point
x = 100 // OK: x is mutable
FeatureSyntaxDescription
Immutable variablelet x = valueCannot be reassigned
Mutable variablelet mut x = valueCan be reassigned
Type annotationlet x: Type = valueExplicit type
Shadowinglet x = ... (again)New variable, same name
Tuple destructuringlet (a, b) = tupleBind tuple elements
Struct destructuringlet S { field } = sBind struct fields