Loops
This chapter covers loop constructs in Psy: while loops and for loops.
While Loops
While loops execute a block of code repeatedly as long as a condition remains true.
Basic While Loop
fn main() -> Felt { let mut res = 0; let mut i = 0; while i < 10 { res += i; i += 1; } res // Returns 45 (0+1+2+...+9) }
Fibonacci Sequence with While
fn fibonacci(a: Felt, b: Felt) -> Felt { let mut res = 0; let mut a = a; let mut b = b; let mut i = 2; let mut c = 0; while i <= 10 { let d = a + b; c = d; a = b; b = c; i += 1; } res = b; return res; } fn main() { let result = fibonacci(2, 3); // Returns 233 }
While with Complex State
fn main() { let mut sum = 0; let mut count = 0; let mut current = 1; while current <= 100 { if current % 2 == 0 { // Even numbers only sum += current; count += 1; } else { // Do nothing for odd numbers }; current += 1; } // sum contains sum of all even numbers from 1 to 100 // count contains how many even numbers were found }
While with Arrays
#![allow(unused)] fn main() { struct Data { pub values: [Felt; 5], pub count: Felt, } fn process_data() -> Data { let mut data = new Data { values: [0; 5], count: 0, }; let mut i = 0; while i < 5 { data.values[i] = i * i; // Square of index data.count += 1; i += 1; } return data; } }
For Loops
For loops provide a way to iterate over ranges with automatic index management.
Basic For Loop
fn main() -> Felt { let mut sum = 0; for n in 0u32..100u32 { sum += (n as Felt); } sum // Returns 4950 (sum of 0 to 99) }
For Loop with Arrays
struct TestSibling { pub value: [Felt; 4], } fn main() { let mut test_array: [TestSibling; 3] = [ new TestSibling { value: [1, 2, 3, 4] }, new TestSibling { value: [5, 6, 7, 8] }, new TestSibling { value: [9, 10, 11, 12] } ]; for i in 0u32..3u32 { let level_index = i as Felt; let sibling = test_array[level_index]; let value = sibling.value; // Process each element let sum = value[0] + value[1] + value[2] + value[3]; // Modify array element test_array[level_index] = new TestSibling { value: [sum, sum, sum, sum] }; } }
For Loop with Conditionals
fn main() { let mut test_array: [TestSibling; 3] = [ new TestSibling { value: [1, 2, 3, 4] }, new TestSibling { value: [0, 0, 0, 0] }, new TestSibling { value: [0, 0, 0, 0] } ]; for i in 0u32..3u32 { let level_index = i as Felt; let sibling = test_array[level_index]; let value = sibling.value; if level_index < 1 { // First element has specific values // value[0] == 1, value[1] == 2, etc. } else { // Other elements are zeros // value[0] == 0, value[1] == 0, etc. } } }
For Loop with Complex Processing
#![allow(unused)] fn main() { fn matrix_multiplication() { let matrix_a: [[Felt; 3]; 3] = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; let matrix_b: [[Felt; 3]; 3] = [ [9, 8, 7], [6, 5, 4], [3, 2, 1] ]; let mut result: [[Felt; 3]; 3] = [[0; 3]; 3]; for i in 0u32..3u32 { for j in 0u32..3u32 { let mut sum = 0; for k in 0u32..3u32 { let ii = i as Felt; let jj = j as Felt; let kk = k as Felt; sum += matrix_a[ii][kk] * matrix_b[kk][jj]; } result[i as Felt][j as Felt] = sum; } } } }
For Loop with Accumulation
#![allow(unused)] fn main() { fn calculate_factorials() -> [Felt; 10] { let mut factorials = [0; 10]; for i in 0u32..10u32 { let mut factorial = 1; let num = (i + 1u32) as Felt; // Calculate factorial of (i+1) for j in 1u32..(i + 2u32) { factorial *= j as Felt; } factorials[i as Felt] = factorial; } return factorials; } }
Loop Patterns
Counting Patterns
#![allow(unused)] fn main() { // Count up fn count_up() { for i in 1u32..11u32 { // 1 to 10 let value = i as Felt; // Process value } } // Count with step fn count_with_step() { for i in 0u32..10u32 { let value = (i * 2) as Felt; // 0, 2, 4, 6, 8, ... // Process even numbers } } // Count down (using while) fn count_down() { let mut i = 10; while i > 0 { // Process i i -= 1; } } }
Array Processing Patterns
#![allow(unused)] fn main() { // Initialize array fn initialize_array() -> [Felt; 5] { let mut arr = [0; 5]; for i in 0u32..5u32 { arr[i as Felt] = (i + 1) as Felt; } return arr; // [1, 2, 3, 4, 5] } // Sum array elements fn sum_array(arr: [Felt; 5]) -> Felt { let mut sum = 0; for i in 0u32..5u32 { sum += arr[i as Felt]; } return sum; } // Find maximum fn find_max(arr: [Felt; 5]) -> Felt { let mut max = arr[0]; for i in 1u32..5u32 { let current = arr[i as Felt]; if current > max { max = current; } } return max; } }
Nested Loop Patterns
#![allow(unused)] fn main() { // Create multiplication table fn multiplication_table() -> [[Felt; 10]; 10] { let mut table = [[0; 10]; 10]; for i in 0u32..10u32 { for j in 0u32..10u32 { let ii = i as Felt; let jj = j as Felt; table[ii][jj] = (ii + 1) * (jj + 1); } } return table; } // Process 2D grid fn process_grid() { let mut grid: [[Felt; 5]; 5] = [[0; 5]; 5]; for row in 0u32..5u32 { for col in 0u32..5u32 { let r = row as Felt; let c = col as Felt; // Set diagonal elements to 1 if r == c { grid[r][c] = 1; } else { grid[r][c] = r + c; } } } } }
Search Patterns
#![allow(unused)] fn main() { // Linear search fn linear_search(arr: [Felt; 10], target: Felt) -> Felt { for i in 0u32..10u32 { if arr[i as Felt] == target { return i as Felt; // Return index } } return 10; // Not found (return invalid index) } // Search with condition fn find_first_even(arr: [Felt; 10]) -> Felt { for i in 0u32..10u32 { let value = arr[i as Felt]; if value % 2 == 0 { return value; } } return 0; // No even number found } }
Loop Control Notes
Range Syntax
For loops in Psy use range syntax:
#![allow(unused)] fn main() { // Inclusive ranges for i in 0u32..5u32 { // i goes from 0 to 4 (5 is excluded) // Loop body } // Must specify u32 type for ranges for i in 1u32..10u32 { // i goes from 1 to 9 let value = i as Felt; // Convert to Felt if needed // Process value } }
Type Conversions in Loops
fn main() { for i in 0u32..10u32 { let index = i as Felt; // Convert u32 to Felt for array indexing let squared = index * index; // Felt arithmetic // Use index for array operations let mut arr = [0; 10]; arr[index] = squared; } }
Loop Variable Scope
fn main() { // Loop variable 'i' is only available inside the loop for i in 0u32..5u32 { let doubled = (i * 2) as Felt; // 'i' and 'doubled' are available here } // 'i' is not available here let mut counter = 0; while counter < 5 { let temp = counter * 2; // 'counter' and 'temp' are available here counter += 1; } // 'counter' is still available here (declared outside loop) // 'temp' is not available here }
Important Considerations
Psy vs Rust Syntax Differences
Psy has some important syntax differences from Rust:
#![allow(unused)] fn main() { // ❌ Psy requires explicit u32 type in arithmetic for i in 0u32..10u32 { let value = (i + 1) as Felt; // Error: type mismatch } // ✅ Correct: use explicit u32 literals for i in 0u32..10u32 { let value = (i + 1u32) as Felt; // Correct } // ❌ If statements as statements need semicolons if condition { // do something } else { // do something else } next_statement(); // Error: missing semicolon // ✅ Correct: add semicolon after if-else statement if condition { // do something } else { // do something else }; // Required semicolon next_statement(); }
No Break or Continue
Psy loops do not support break or continue statements. Loop control must be managed through conditions:
#![allow(unused)] fn main() { // ❌ Not supported // for i in 0u32..10u32 { // if condition { // break; // } // } // ✅ Use conditional logic instead fn controlled_loop() { let mut should_continue = true; let mut i = 0; while i < 10 && should_continue { // Process logic if some_condition { should_continue = false; // Equivalent to break } else { // Process normally } i += 1; } } }
Loop Unrolling for ZK
Since Psy compiles to zero-knowledge circuits, loops are unrolled at compile time. This means:
- Fixed iterations: Loop bounds must be known at compile time
- No dynamic bounds: Cannot loop based on runtime values
- Resource usage: Each loop iteration becomes part of the circuit
#![allow(unused)] fn main() { // ✅ Valid: Fixed bounds known at compile time for i in 0u32..100u32 { // Loop body } // ❌ Invalid: Runtime-dependent bounds fn invalid_loop(n: Felt) { // This would not work as n is not known at compile time // for i in 0u32..n { // // Loop body // } } }
Key Points
- While loops execute while a condition is true
- For loops iterate over fixed ranges with u32 indices
- No break/continue - use conditional logic instead
- Fixed bounds - loop iterations must be compile-time constant
- Type conversion - convert u32 loop indices to Felt for array access
- Nested loops are fully supported
- Loop unrolling - all loops are unrolled in the final circuit
- Scope rules - loop variables have block scope
- Syntax differences from Rust:
- u32 arithmetic requires explicit
u32literals (e.g.,i + 1u32) - If-else statements used as statements need trailing semicolons
- Empty else blocks should contain at least one statement
- u32 arithmetic requires explicit