3/5
## Understanding Rust's Basic `loop`: Infinite Iteration and Control In Rust, the most fundamental way to create a repetitive block of code is with the `loop` keyword. By default, this construct creates an infinite loop, continuously executing the code within its block. Consider this initial example: ```rust fn main() { loop { println!("loop"); } } ``` If you compile and run this program, it will print "loop" to your terminal indefinitely. You'll need to manually stop it, typically using Ctrl+C. While infinite loops have their uses (e.g., in servers or embedded systems waiting for events), most practical scenarios require loops that eventually terminate. To achieve this with the `loop` keyword, we introduce a counter and a `break` statement. The `break` keyword immediately exits the current loop. Let's see how to control a `loop`: ```rust fn main() { let mut i = 0; // Declare a mutable counter, initialized to 0 loop { println!("loop {}", i); // Print the current value of the counter i += 1; // Increment the counter if i > 5 { // Condition to exit the loop break; // Execute break to exit the loop } } // Execution continues here after the loop breaks println!("Loop finished."); } ``` In this revised example: * `let mut i = 0;`: We declare a variable `i` and mark it as `mut` (mutable) because its value will change within the loop. * `i += 1;`: In each iteration, we increment `i`. * `if i > 5 { break; }`: This is our termination condition. When `i` becomes 6 (i.e., `i > 5` is true), the `break` statement is executed, and the loop stops. The output of this controlled loop will be: ``` loop 0 loop 1 loop 2 loop 3 loop 4 loop 5 Loop finished. ``` The loop executes for `i` values from 0 up to and including 5. Once `i` is 5, "loop 5" is printed, `i` increments to 6, the condition `i > 5` becomes true, and the loop terminates. ## Conditional Execution: Mastering the `while` Loop in Rust Another common way to control loop execution is with a `while` loop. A `while` loop continues to execute its block of code as long as a specified boolean condition remains true. The condition is checked *before* each iteration. Here's an example that achieves the same outcome as our previous controlled `loop`: ```rust fn main() { let mut i = 0; // Re-initialize or use a new counter while i <= 5 { // Loop continues as long as i is less than or equal to 5 println!("while loop {}", i); i += 1; // Increment the counter } println!("While loop finished."); } ``` The `while i <= 5` line dictates that the loop will run as long as `i` is less than or equal to 5. Once `i` becomes 6, the condition `6 <= 5` evaluates to false, and the loop terminates. The output will be: ``` while loop 0 while loop 1 while loop 2 while loop 3 while loop 4 while loop 5 While loop finished. ``` ## Effortless Iteration: Exploring `for` Loops and Ranges in Rust Rust's `for` loop is particularly well-suited for iterating over a sequence of items, such as a range of numbers or the elements within a collection. It's often considered more idiomatic and safer than manual index management with `loop` or `while` for these tasks. To iterate a specific number of times, you can use a `for` loop with a range: ```rust fn main() { for i in 0..6 { // Iterates from 0 up to (but not including) 6 println!("for loop {}", i); } println!("For loop (exclusive range) finished."); // For an inclusive range (0 to 5 inclusive): for i in 0..=5 { println!("for loop inclusive {}", i); } println!("For loop (inclusive range) finished."); } ``` * `0..6`: This creates a range that starts at 0 and goes up to, but does not include, 6. So, it includes the numbers 0, 1, 2, 3, 4, and 5. * `0..=5`: This syntax creates an inclusive range, meaning it includes 0, 1, 2, 3, 4, and 5. The output for the first `for` loop (`0..6`): ``` for loop 0 for loop 1 for loop 2 for loop 3 for loop 4 for loop 5 For loop (exclusive range) finished. ``` And for the second `for` loop (`0..=5`): ``` for loop inclusive 0 for loop inclusive 1 for loop inclusive 2 for loop inclusive 3 for loop inclusive 4 for loop inclusive 5 For loop (inclusive range) finished. ``` Both produce the same sequence of numbers in this specific case. ## Iterating Over Arrays in Rust: Indexing vs. Direct Access Arrays in Rust are fixed-size collections of elements of the same type. `for` loops provide convenient ways to iterate through them. First, let's declare an array: ```rust let arr = [10, 20, 30, 40, 50]; ``` **Method 1: Index-based `for` loop** You can iterate over an array using its indices, similar to how you might in other languages. ```rust fn main() { let arr = [1, 2, 3, 4, 5]; let n: usize = arr.len(); // Get the length of the array. for i in 0..n { // Iterate from index 0 to n-1 println!("arr index: {}, value: {}", i, arr[i]); // Access element by index } } ``` * `arr.len()`: This method returns the number of elements in the array. * `let n: usize`: The length of an array (and indices) in Rust is of type `usize`. This type is platform-dependent and large enough to represent the size of any collection in memory. * `arr[i]`: This syntax accesses the element at index `i` in the array. The output will be: ``` arr index: 0, value: 1 arr index: 1, value: 2 arr index: 2, value: 3 arr index: 3, value: 4 arr index: 4, value: 5 ``` **Method 2: Direct Iteration over Elements (More Idiomatic Rust)** A more Rusty and often preferred way to iterate over an array is to directly access its elements: ```rust fn main() { let arr = [1, 2, 3, 4, 5]; for element_value in arr { // Iterates directly over the values in 'arr' println!("arr value: {}", element_value); } } ``` This loop iterates through each element of `arr`, assigning its value to `element_value` in each iteration. For arrays containing simple types (like integers) that implement the `Copy` trait, this iteration behaves as if it's working on copies of the elements, and the original array remains usable. The output for this method: ``` arr value: 1 arr value: 2 arr value: 3 arr value: 4 arr value: 5 ``` This direct iteration is generally less error-prone as it avoids manual index management and potential off-by-one errors. ## Working with Vectors: Rust's `for` Loop, Ownership, and `iter()` Vectors (`Vec<T>`) are dynamically-sized, growable collections in Rust, akin to arrays that can change their size. Iterating over vectors introduces an important Rust concept: ownership. Let's declare a vector: ```rust let v = vec![10, 20, 30, 40, 50]; ``` **Direct Iteration and Ownership** If you iterate over a vector directly using a `for` loop like this: ```rust fn main() { let v = vec![10, 20, 30, 40, 50]; // First loop: for n_val in v { // This loop takes ownership of 'v' println!("vec {}", n_val); } // Attempting to use 'v' again will cause a compile-time error: // println!("Vector length after loop: {}", v.len()); // ERROR: value used here after move // for n_val in v { // ERROR: value used here after move // println!("vec again {}", n_val); // } } ``` When you write `for n_val in v`, Rust implicitly calls a method called `into_iter()` on the vector `v`. The `into_iter()` method consumes the vector, taking ownership of it. This means that after the loop finishes, the vector `v` is no longer valid in the current scope; it has been "moved" into the loop and dropped (its memory deallocated) when the loop concludes. Any attempt to use `v` after this loop will result in a compile-time "value used here after move" error, a key safety feature of Rust preventing use-after-free bugs. The output of the first (and only successful) loop: ``` vec 10 vec 20 vec 30 vec 40 vec 50 ``` **Iterating Multiple Times using `.iter()` (Borrowing)** If you need to iterate over a vector multiple times, or use the vector after the loop, you must borrow it instead of letting the loop consume it. This is done using the `.iter()` method. ```rust fn main() { let v = vec![10, 20, 30, 40, 50]; // First loop using .iter() for n_val_ref in v.iter() { // '.iter()' borrows 'v' println!("vec ref {}", n_val_ref); // n_val_ref is a reference (e.g., &i32) } // Second loop using .iter() - this is now allowed because 'v' was only borrowed for n_val_ref in v.iter() { println!("vec ref again {}", n_val_ref); } println!("Vector is still valid, length: {}", v.len()); } ``` * `v.iter()`: This method returns an iterator that yields *references* to the elements in the vector (e.g., `&i32` if `v` contains `i32`). The original vector `v` is only borrowed for the duration of the loop and remains valid and owned by its original scope. * Because `n_val_ref` is a reference, if you need the actual value, you might need to dereference it (e.g., `*n_val_ref`), though `println!` often handles this automatically for display purposes. The output will be: ``` vec ref 10 vec ref 20 vec ref 30 vec ref 40 vec ref 50 vec ref again 10 vec ref again 20 vec ref again 30 vec ref again 40 vec ref 50 Vector is still valid, length: 5 ``` Understanding the distinction between `into_iter()` (takes ownership) and `iter()` (borrows) is crucial for working effectively with collections in Rust. If you need to modify the elements within the loop, you would use `.iter_mut()`, which provides mutable references. ## Powerful Loops: Returning Values from `loop` Expressions in Rust In Rust, many constructs are expressions, meaning they evaluate to a value. This includes the basic `loop` construct. You can use a `loop` to compute a value and return it, which can then be assigned to a variable. The value to be returned from the loop is specified after the `break` keyword. ```rust fn main() { let mut i = 0; let result_string: &str = loop { // The loop expression is assigned to 'result_string' println!("loop computation {}", i); i += 1; if i > 5 { break "loop computation ends here"; // Return this string literal } }; // Note the semicolon: `let ... = loop { ... };` is a statement. println!("Loop returned: {}", result_string); } ``` Key points in this example: * `let result_string: &str = loop { ... };`: The entire `loop { ... }` block is an expression. Its resulting value is assigned to `result_string`. * The type of `result_string` is explicitly annotated as `&str` (a string slice reference) because the loop is set up to return a string literal. Rust can often infer this, but explicit annotation is good practice here. * `break "loop computation ends here";`: When `i` exceeds 5, the loop terminates. The `break` statement not only exits the loop but also provides the value `"loop computation ends here"`, which becomes the result of the entire `loop` expression. * Semicolon: Since `let result_string = ...;` is a statement, the `loop` block (when used as an expression in an assignment) must be followed by a semicolon. The output of this code will be: ``` loop computation 0 loop computation 1 loop computation 2 loop computation 3 loop computation 4 loop computation 5 Loop returned: loop computation ends here ``` This ability for `loop` to return values allows for concise and expressive code, especially when a loop's primary purpose is to compute a result that's needed afterwards. Remember that only the `loop` keyword supports returning values with `break`; `while` and `for` loops do not directly return values in this manner (they evaluate to `()`, the unit type).
loop
: Infinite Iteration and ControlIn Rust, the most fundamental way to create a repetitive block of code is with the loop
keyword. By default, this construct creates an infinite loop, continuously executing the code within its block.
Consider this initial example:
If you compile and run this program, it will print "loop" to your terminal indefinitely. You'll need to manually stop it, typically using Ctrl+C.
While infinite loops have their uses (e.g., in servers or embedded systems waiting for events), most practical scenarios require loops that eventually terminate. To achieve this with the loop
keyword, we introduce a counter and a break
statement. The break
keyword immediately exits the current loop.
Let's see how to control a loop
:
In this revised example:
let mut i = 0;
: We declare a variable i
and mark it as mut
(mutable) because its value will change within the loop.
i += 1;
: In each iteration, we increment i
.
if i > 5 { break; }
: This is our termination condition. When i
becomes 6 (i.e., i > 5
is true), the break
statement is executed, and the loop stops.
The output of this controlled loop will be:
The loop executes for i
values from 0 up to and including 5. Once i
is 5, "loop 5" is printed, i
increments to 6, the condition i > 5
becomes true, and the loop terminates.
while
Loop in RustAnother common way to control loop execution is with a while
loop. A while
loop continues to execute its block of code as long as a specified boolean condition remains true. The condition is checked before each iteration.
Here's an example that achieves the same outcome as our previous controlled loop
:
The while i <= 5
line dictates that the loop will run as long as i
is less than or equal to 5. Once i
becomes 6, the condition 6 <= 5
evaluates to false, and the loop terminates.
The output will be:
for
Loops and Ranges in RustRust's for
loop is particularly well-suited for iterating over a sequence of items, such as a range of numbers or the elements within a collection. It's often considered more idiomatic and safer than manual index management with loop
or while
for these tasks.
To iterate a specific number of times, you can use a for
loop with a range:
0..6
: This creates a range that starts at 0 and goes up to, but does not include, 6. So, it includes the numbers 0, 1, 2, 3, 4, and 5.
0..=5
: This syntax creates an inclusive range, meaning it includes 0, 1, 2, 3, 4, and 5.
The output for the first for
loop (0..6
):
And for the second for
loop (0..=5
):
Both produce the same sequence of numbers in this specific case.
Arrays in Rust are fixed-size collections of elements of the same type. for
loops provide convenient ways to iterate through them.
First, let's declare an array:
Method 1: Index-based for
loop
You can iterate over an array using its indices, similar to how you might in other languages.
arr.len()
: This method returns the number of elements in the array.
let n: usize
: The length of an array (and indices) in Rust is of type usize
. This type is platform-dependent and large enough to represent the size of any collection in memory.
arr[i]
: This syntax accesses the element at index i
in the array.
The output will be:
Method 2: Direct Iteration over Elements (More Idiomatic Rust)
A more Rusty and often preferred way to iterate over an array is to directly access its elements:
This loop iterates through each element of arr
, assigning its value to element_value
in each iteration. For arrays containing simple types (like integers) that implement the Copy
trait, this iteration behaves as if it's working on copies of the elements, and the original array remains usable.
The output for this method:
This direct iteration is generally less error-prone as it avoids manual index management and potential off-by-one errors.
for
Loop, Ownership, and iter()
Vectors (Vec<T>
) are dynamically-sized, growable collections in Rust, akin to arrays that can change their size. Iterating over vectors introduces an important Rust concept: ownership.
Let's declare a vector:
Direct Iteration and Ownership
If you iterate over a vector directly using a for
loop like this:
When you write for n_val in v
, Rust implicitly calls a method called into_iter()
on the vector v
. The into_iter()
method consumes the vector, taking ownership of it. This means that after the loop finishes, the vector v
is no longer valid in the current scope; it has been "moved" into the loop and dropped (its memory deallocated) when the loop concludes. Any attempt to use v
after this loop will result in a compile-time "value used here after move" error, a key safety feature of Rust preventing use-after-free bugs.
The output of the first (and only successful) loop:
Iterating Multiple Times using .iter()
(Borrowing)
If you need to iterate over a vector multiple times, or use the vector after the loop, you must borrow it instead of letting the loop consume it. This is done using the .iter()
method.
v.iter()
: This method returns an iterator that yields references to the elements in the vector (e.g., &i32
if v
contains i32
). The original vector v
is only borrowed for the duration of the loop and remains valid and owned by its original scope.
Because n_val_ref
is a reference, if you need the actual value, you might need to dereference it (e.g., *n_val_ref
), though println!
often handles this automatically for display purposes.
The output will be:
Understanding the distinction between into_iter()
(takes ownership) and iter()
(borrows) is crucial for working effectively with collections in Rust. If you need to modify the elements within the loop, you would use .iter_mut()
, which provides mutable references.
loop
Expressions in RustIn Rust, many constructs are expressions, meaning they evaluate to a value. This includes the basic loop
construct. You can use a loop
to compute a value and return it, which can then be assigned to a variable.
The value to be returned from the loop is specified after the break
keyword.
Key points in this example:
let result_string: &str = loop { ... };
: The entire loop { ... }
block is an expression. Its resulting value is assigned to result_string
.
The type of result_string
is explicitly annotated as &str
(a string slice reference) because the loop is set up to return a string literal. Rust can often infer this, but explicit annotation is good practice here.
break "loop computation ends here";
: When i
exceeds 5, the loop terminates. The break
statement not only exits the loop but also provides the value "loop computation ends here"
, which becomes the result of the entire loop
expression.
Semicolon: Since let result_string = ...;
is a statement, the loop
block (when used as an expression in an assignment) must be followed by a semicolon.
The output of this code will be:
This ability for loop
to return values allows for concise and expressive code, especially when a loop's primary purpose is to compute a result that's needed afterwards. Remember that only the loop
keyword supports returning values with break
; while
and for
loops do not directly return values in this manner (they evaluate to ()
, the unit type).
An in-depth guide to Rust's Looping Constructs - Command `loop`, `while`, and `for` to orchestrate code repetition, and navigate ranges, arrays, and vectors effectively. Master vector iteration with `into_iter()` (ownership) vs. `iter()` (borrowing), and leverage `loop` expressions to return computed values.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025