Control Flow
Control flow statements allow you to control the execution path of your Move programs. Move provides several control flow constructs that enable conditional execution, loops, and pattern matching.
If Statements
If statements allow you to execute different code blocks based on whether a condition is true or false.
Basic If Statement
module my_module::control_flow {
public fun basic_if(x: u64): u64 {
if (x > 10) {
x * 2
} else {
x
}
}
}
If-Else Statement
public fun if_else_example(age: u64): String {
if (age < 18) {
string::utf8(b"Minor")
} else if (age < 65) {
string::utf8(b"Adult")
} else {
string::utf8(b"Senior")
}
}
If Statement with Multiple Conditions
public fun complex_condition(x: u64, y: u64): bool {
if (x > 0 && y > 0 && x + y < 100) {
true
} else {
false
}
}
If Statement with Function Calls
public fun validate_input(input: u64): bool {
if (input == 0) {
return false
};
if (input > 1000) {
return false
};
true
}
While Loops
While loops execute a block of code repeatedly as long as a condition remains true.
Basic While Loop
public fun count_down(n: u64): u64 {
let counter = n;
while (counter > 0) {
counter = counter - 1;
};
counter
}
While Loop with Break
public fun find_first_positive(numbers: vector<u64>): u64 {
let i = 0;
let len = vector::length(&numbers);
let result = 0u64;
while (i < len) {
let current = *vector::borrow(&numbers, i);
if (current > 0) {
result = current;
break
};
i = i + 1;
};
result
}
While Loop with Continue
public fun sum_positive_numbers(numbers: vector<u64>): u64 {
let i = 0;
let len = vector::length(&numbers);
let sum = 0u64;
while (i < len) {
let current = *vector::borrow(&numbers, i);
i = i + 1;
if (current == 0) {
continue
};
sum = sum + current;
};
sum
}
Infinite While Loop with Break
public fun find_element(numbers: vector<u64>, target: u64): bool {
let i = 0;
let len = vector::length(&numbers);
while (true) {
if (i >= len) {
break false
};
let current = *vector::borrow(&numbers, i);
if (current == target) {
break true
};
i = i + 1;
}
}
For Loops
For loops allow you to iterate over ranges or collections. Move supports for loops with range syntax.
Basic For Loop with Range
public fun sum_range(start: u64, end: u64): u64 {
let sum = 0u64;
for (i in start..end) {
sum = sum + i;
};
sum
}
For Loop with Step
public fun sum_even_numbers(max: u64): u64 {
let sum = 0u64;
for (i in 0..max) {
if (i % 2 == 0) {
sum = sum + i;
};
};
sum
}
For Loop with Vector Iteration
public fun sum_vector_elements(numbers: vector<u64>): u64 {
let sum = 0u64;
let len = vector::length(&numbers);
for (i in 0..len) {
let element = *vector::borrow(&numbers, i);
sum = sum + element;
};
sum
}
For Loop with Early Exit
public fun find_index(numbers: vector<u64>, target: u64): u64 {
let len = vector::length(&numbers);
for (i in 0..len) {
let current = *vector::borrow(&numbers, i);
if (current == target) {
return i
};
};
len // Return length if not found
}
Nested For Loops
public fun matrix_sum(matrix: vector<vector<u64>>): u64 {
let sum = 0u64;
let rows = vector::length(&matrix);
for (i in 0..rows) {
let row = vector::borrow(&matrix, i);
let cols = vector::length(row);
for (j in 0..cols) {
let element = *vector::borrow(row, j);
sum = sum + element;
};
};
sum
}
Match Statements
Match statements provide pattern matching capabilities, primarily used with enums. They allow you to execute different code based on the variant of an enum.
Basic Match Statement
module my_module::enums {
enum Status {
Active,
Inactive,
Pending,
}
public fun get_status_message(status: Status): String {
match (status) {
Status::Active => string::utf8(b"User is active"),
Status::Inactive => string::utf8(b"User is inactive"),
Status::Pending => string::utf8(b"User status is pending"),
}
}
}
Match Statement with Data-Carrying Enums
enum Result<T> {
Ok(T),
Err(String),
}
public fun handle_result(result: Result<u64>): String {
match (result) {
Result::Ok(value) => string::utf8(b"Success: ") + std::to_string(value),
Result::Err(message) => string::utf8(b"Error: ") + message,
}
}
Match Statement with Complex Patterns
enum Shape {
Circle(u64), // radius
Rectangle(u64, u64), // width, height
Square(u64), // side
}
public fun calculate_area(shape: Shape): u64 {
match (shape) {
Shape::Circle(radius) => {
// Approximate area calculation (π * r²)
radius * radius * 3
},
Shape::Rectangle(width, height) => {
width * height
},
Shape::Square(side) => {
side * side
},
}
}
Match Statement with Guards
enum Number {
Zero,
Positive(u64),
Negative(u64),
}
public fun classify_number(num: Number): String {
match (num) {
Number::Zero => string::utf8(b"Zero"),
Number::Positive(value) => {
if (value < 10) {
string::utf8(b"Small positive")
} else {
string::utf8(b"Large positive")
}
},
Number::Negative(value) => {
if (value > 10) {
string::utf8(b"Large negative")
} else {
string::utf8(b"Small negative")
}
},
}
}
Match Statement with Default Case
enum Direction {
North,
South,
East,
West,
}
public fun get_direction_name(direction: Direction): String {
match (direction) {
Direction::North => string::utf8(b"North"),
Direction::South => string::utf8(b"South"),
Direction::East => string::utf8(b"East"),
Direction::West => string::utf8(b"West"),
}
}
Match Statement in Error Handling
enum ValidationResult {
Valid,
InvalidAge,
InvalidName,
InvalidEmail,
}
public fun validate_user(age: u64, name: String, email: String): ValidationResult {
if (age < 18 || age > 120) {
return ValidationResult::InvalidAge
};
if (string::length(&name) == 0) {
return ValidationResult::InvalidName
};
if (string::length(&email) == 0) {
return ValidationResult::InvalidEmail
};
ValidationResult::Valid
}
public fun get_validation_message(result: ValidationResult): String {
match (result) {
ValidationResult::Valid => string::utf8(b"User data is valid"),
ValidationResult::InvalidAge => string::utf8(b"Age must be between 18 and 120"),
ValidationResult::InvalidName => string::utf8(b"Name cannot be empty"),
ValidationResult::InvalidEmail => string::utf8(b"Email cannot be empty"),
}
}
Best Practices
If Statements
- Use early returns: Return early to reduce nesting
- Keep conditions simple: Break complex conditions into multiple if statements
- Use meaningful variable names: Make conditions self-documenting
// Good: Early return
public fun validate_input(input: u64): bool {
if (input == 0) {
return false
};
if (input > 1000) {
return false
};
true
}
// Bad: Deep nesting
public fun validate_input_bad(input: u64): bool {
if (input != 0) {
if (input <= 1000) {
return true
} else {
return false
}
} else {
return false
}
}
While Loops
- Ensure termination: Make sure loops will eventually terminate
- Use break for early exit: Use break instead of complex conditions
- Initialize variables properly: Initialize loop variables before the loop
// Good: Clear termination condition
public fun safe_loop(max_iterations: u64): u64 {
let i = 0;
while (i < max_iterations) {
i = i + 1;
};
i
}
// Bad: Potential infinite loop
public fun unsafe_loop(): u64 {
let i = 0;
while (i >= 0) { // This will never be false for u64
i = i + 1;
};
i
}
For Loops
- Use for loops for known ranges: Prefer for loops when you know the iteration count
- Avoid modifying loop variables: Don't modify the loop variable inside the loop
- Use meaningful variable names: Use descriptive names for loop variables
// Good: Clear iteration
public fun process_array(data: vector<u64>): u64 {
let sum = 0u64;
let len = vector::length(&data);
for (index in 0..len) {
let element = *vector::borrow(&data, index);
sum = sum + element;
};
sum
}
Match Statements
- Handle all cases: Ensure all enum variants are covered
- Use meaningful patterns: Use descriptive variable names in patterns
- Keep match arms simple: Extract complex logic into separate functions
// Good: All cases handled
public fun process_status(status: Status): String {
match (status) {
Status::Active => string::utf8(b"Active"),
Status::Inactive => string::utf8(b"Inactive"),
Status::Pending => string::utf8(b"Pending"),
}
}
// Bad: Missing case (this would cause a compilation error)
public fun process_status_bad(status: Status): String {
match (status) {
Status::Active => string::utf8(b"Active"),
Status::Inactive => string::utf8(b"Inactive"),
// Missing Status::Pending case
}
}
Common Patterns
Error Handling Pattern
enum OperationResult {
Success(u64),
Failure(String),
}
public fun safe_divide(a: u64, b: u64): OperationResult {
if (b == 0) {
return OperationResult::Failure(string::utf8(b"Division by zero"))
};
OperationResult::Success(a / b)
}
public fun handle_division_result(result: OperationResult): String {
match (result) {
OperationResult::Success(value) => {
string::utf8(b"Result: ") + std::to_string(value)
},
OperationResult::Failure(error) => {
string::utf8(b"Error: ") + error
},
}
}
State Machine Pattern
enum GameState {
Waiting,
Playing,
Paused,
GameOver,
}
public fun update_game_state(current_state: GameState, action: String): GameState {
match (current_state) {
GameState::Waiting => {
if (action == string::utf8(b"start")) {
GameState::Playing
} else {
GameState::Waiting
}
},
GameState::Playing => {
if (action == string::utf8(b"pause")) {
GameState::Paused
} else if (action == string::utf8(b"end")) {
GameState::GameOver
} else {
GameState::Playing
}
},
GameState::Paused => {
if (action == string::utf8(b"resume")) {
GameState::Playing
} else if (action == string::utf8(b"end")) {
GameState::GameOver
} else {
GameState::Paused
}
},
GameState::GameOver => GameState::GameOver,
}
}
By understanding and using these control flow constructs effectively, you can write more expressive and maintainable Move code that handles complex logic and decision-making scenarios.