Array iteration in Rust
Amit Nadiger
Polyglot(Rust??, C++ 11,14,17,20, C, Kotlin, Java) Android TV, Cas, Blockchain, Polkadot, UTXO, Substrate, Wasm, Proxy-wasm,AndroidTV, Dvb, STB, Linux, Engineering management.
There are many different loops used in this code snippet, each with a different way of iterating over the array. Here is a brief explanation of each of them:
Indexing loop:
for i in 0..array.len(): This loop iterates over the indices of the array, from 0 to the length of the array. The loop variable i takes on the value of each index in turn, and the array element can be accessed using the square bracket notation array[i]. This loop does not involve any references or ownership, and the elements are accessed by value. This loop gives you direct access to the elements of the array, but it is less concise than the other loops.
for i in 0..array.len() {
println!("{}",array[i]);
}
- Advantage: It allows you to loop through an array using the index of each element, which can be useful in certain situations where you need to access the index directly.
- Disadvantage: This loop is less flexible compared to the other loops and it doesn't provide any extra functionality for working with arrays. It also requires more code to access the elements of the array compared to other loops.
- Suitable scenarios: This loop is useful when you need to access the index of each element directly or if you need to perform a specific operation on each element based on its index.
----------------------------------------------------->
Immutable reference loop:
for i in &array: This loop iterates over the array using an immutable reference to the array. The reference is created using the & operator, which borrows the array and creates an immutable reference to it. The loop is able to access the elements of the array through the reference. The loop variable i is a reference to each element of the array in turn, and the elements can be accessed by dereferencing the reference using the * operator, like *i. This loop does not take ownership of the array, and it allows the elements to be accessed without copying. This loop is more concise than the indexing loop, but it does not allow you to modify the elements of the array.
for i in &array {
println!("{}",i);
}
- Advantage: This loop allows you to loop through an array using a reference to each element. This is useful when you don't need to modify the elements of the array.
- Disadvantage: This loop is less flexible compared to the other loops and it doesn't provide any extra functionality for working with arrays.
- Suitable scenarios: This loop is useful when you need to read the elements of an array without modifying them.
----------------------------------------------------->
Iterator loop:
for i in array.iter(): This loop iterates over the elements of the array using the iter() method, which returns an iterator over the elements of the array.
The iterator is obtained by calling the iter method on the array. The loop is able to access the elements of the array through the iterator. The loop variable i takes on the value of each element in turn. This loop does not involve any references or ownership, and the elements are accessed by value.
This loop is more concise than the previous two loops and provides all the functionality of the immutable reference loop.
for i in array.iter() {
println!("{}",i);
}
- Advantage: This loop allows you to loop through an array using an iterator, which provides a lot of flexibility and extra functionality for working with arrays such as mapping, filtering, folding, and rearranging,fold,filter,map,etc . It also allows you to modify the elements of the array if needed.
- Disadvantage: This loop requires more code compared to the other loops and it may not be suitable for simple operations on arrays.
- Suitable scenarios: This loop is useful when you need to perform complex operations on arrays or when you need to modify the elements of an array.
----------------------------------------------------->
Consuming iterator loop:
for i in array.into_iter(): This loop also uses an iterator to iterate over the array. The iterator is obtained by calling the into_iter method on the array, which consumes the array and creates an iterator that takes ownership of the elements of the array. The loop is able to access the elements of the array through the iterator. The loop variable i takes on the value of each element in turn, and the ownership of the array is transferred to the iterator. This loop is the most concise of all, this loop allows the elements to be accessed without copying, but it consumes the array and prevents its further use.
But above statement of array consumption is not entirely correct please hold-on and see below examples, then it will be clear.
for i in array.into_iter() {
println!("{}",i);
}
Based on the above statement, below code should give error, right ?
fn main() {
? ? let mut array = [1,2,3,4,5,6];
? ? for i in array.into_iter() {
? ? ? ? println!("{}",i);?
? ? }? ?
? ? array[0] = 100;
? ? println!("{:?}",array);
}
/*
Based on the above statement error is expcetd right .
In reality error is not happened
Actual o/p ius below :
1
2
3
4
5
6
[100, 2, 3, 4, 5, 6]
*/
Did you notice why above code didn't throw compilation error ,even though we are using the original array even after moved or consumed:
This is because when you iterate over an array using into_iter(), ownership of the array is transferred to the iterator. This means that you can no longer access the array using the original variable name after the iterator has been consumed.
However, in above case, this is not true for array. But when you use the into_iter() to vector the compilation error happens as described above i.e ownership transferred due to move
Please see below code:
fn main() {
let array = vec![1, 2, 3, 4, 5];
let iter = array.into_iter();
for i in iter {
? ? println!("{}", i);
}
println!("{:?}", array); // This will cause a compile-time error
}
/*
error[E0382]: borrow of moved value: `array`
? --> main.rs:12:19
? ?|
5? |? ? ?let array = vec![1, 2, 3, 4, 5];
? ?|? ? ? ? ?----- move occurs because `array` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
6? |? ? ?let iter = array.into_iter();
? ?|? ? ? ? ? ? ? ? ----- value moved here
...
12 |? ? ?println!("{:?}", array); // This will cause a compile-time error
? ?|? ? ? ? ? ? ? ? ? ? ? ^^^^^ value borrowed here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
*/
In the above example, array is a Vec rather than an array, but the concept is the same. After array is consumed into an iterator with into_iter(), it is no longer available to be accessed. Attempting to access it with println!("{:?}", array) will result in a compile-time error with the message "use of moved value: array".
Why this change in behavior of into_iter() between array and vector?
The into_iter() method is part of the IntoIterator trait in Rust. This trait allows you to convert an object into an iterator by defining the into_iter() method.
The into_iter() method takes ownership of the object and returns an iterator that produces values of the object's type. For an array, the returned iterator has the type std::array::IntoIter<T, N>, while for a vector it has the type std::vec::IntoIter<T>.
You can find more information about the IntoIterator trait and the into_iter() method in the Rust documentation: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html
Arrays in Rust can be iterated over using the IntoIterator trait, just like any other collection type. When an array is passed to a function that expects an IntoIterator parameter, it will automatically be converted into an iterator using the into_iter() method.
arrays implement the IntoIterator trait differently from other types, such as vectors. The IntoIterator trait allows types to be converted into an iterator, which provides a way to iterate over a collection.
When an array is consumed by its into_iter() method, the array is automatically converted into an iterator that produces references to each element. This does not transfer ownership of the array, and the original array can still be accessed after the iterator is consumed.
In other words, the original array is not moved into the iterator, but rather it is converted into an iterator that borrows from the array. This is different from what happens when a vector is consumed by its into_iter() method, as in that case, the vector is moved into the iterator, transferring ownership.
So, in conclusion, the difference in behavior is because Rust treats arrays and vectors differently when implementing the IntoIterator trait.
- Suitable scenarios: This loop is useful when you want to modify the elements of an array in place or when you want to move ownership of the array to another function.
----------------------------------------------------->
Using the iter_mut :
for element in array.iter_mut(): This loop iterates over the elements of the array using the iter_mut() method, which returns an iterator over mutable references to the elements of the array. The loop variable element is a mutable reference to each element in turn, and the elements can be modified using the * operator, like *element *= 2. This loop does not consume the array or take ownership of it, but it borrows the array mutably, allowing its elements to be modified.
let mut array = [1, 2, 3, 4, 5];
for element in array.iter_mut() {
*element *= 2;
}
println!("{:?}", array); // [2, 4, 6, 8, 10]
This loop is used when we want to iterate over a mutable reference of an array. It returns an iterator over mutable references to the values in an array.
The advantage of using this loop is that it allows us to modify the values in the array while iterating.
The disadvantage is that it can lead to concurrency issues if multiple threads try to modify the same values simultaneously.
The suitable scenario for using this loop is when we need to perform an operation on each element of an array.
领英推è
----------------------------------------------------->
Using the chunks :
for chunk in array.chunks(n): This loop iterates over the array in chunks of a specified size using the chunks() method, which returns an iterator over non-overlapping chunks of the specified size(n). The loop variable chunk is an array slice containing the elements of each chunk in turn. This loop does not involve any references or ownership, and the elements are accessed by reference.
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for chunk in array.chunks(3) {
println!("chunk {:?}", chunk);
for &number in chunk {
? ? ? ? // Access individual elements in the chunk
? ? println!("Number: {}", number);
? ? }
}
/*
Output:
Chunk[1, 2, 3]
Number: 1
Number: 2
Number: 3
Chunk[4, 5, 6]
Number: 4
Number: 5
Number: 6
Chunk[7, 8, 9]
Number: 7
Number: 8
Number: 9
Chunk[10]
Number: 10
*/
This loop is used when we want to divide an array into chunks of a given size. It returns an iterator over arrays with a fixed size.
The advantage of using this loop is that it allows us to process arrays in smaller, more manageable pieces.
The disadvantage is that it may not cover the entire array if the length of the array is not a multiple of the chunk size.
The suitable scenario for using this loop is when we need to process an array in smaller chunks.
----------------------------------------------------->
Using the windows :
for window in array.windows(3): This loop iterates over the array in windows of a specified size using the windows() method, which returns an iterator over overlapping windows of the specified size. The loop variable window is an array slice containing the elements of each window in turn. This loop does not involve any references or ownership, and the elements are accessed by reference.
let array = [1, 2, 3, 4, 5];
for chunk in array.windows(3) {
println!("window{:?}", chunk);
for &number in chunk {
? ? // Access individual elements in the chunk
? ? println!("Number: {}", number);
? ? }
}
/* Output:
window[1, 2, 3]
Number: 1
Number: 2
Number: 3
window[2, 3, 4]
Number: 2
Number: 3
Number: 4
window[3, 4, 5]
Number: 3
Number: 4
Number: 5
*/
This loop is used when we want to iterate over an array in sliding windows of a given size. It returns an iterator over arrays of a fixed size that are moved forward by one element at a time.
The advantage of using this loop is that it allows us to process an array in overlapping windows of a fixed size.
The disadvantage is that the window size must be less than or equal to the array size.
The suitable scenario for using this loop is when we need to process an array in overlapping windows.
----------------------------------------------------->
Using the iter().enumerate():
for (index, element) in array.iter().enumerate(): This loop iterates over the elements of the array using the iter() method and returns an iterator over the enumerated elements of the array. The loop variable index takes on the value of the index of each element in turn, and the loop variable element takes on the value of each element in turn. This loop does not involve any references or ownership, and the elements are accessed by value.
let array = [1, 2, 3, 4, 5];
for (index, element) in array.iter().enumerate() {
? ? println!("Index {}: {}", index, element);
}
// Output:
// Index 0: 1
// Index 1: 2
// Index 2: 3
// Index 3: 4
// Index 4: 5
This loop is used when we want to iterate over an array while keeping track of the index. It returns an iterator over tuples containing the index and the value.
The advantage of using this loop is that it allows us to access both the index and the value of each element in an array.
The disadvantage is that it can be slower than other loops since it creates a new tuple for each element in the array.
The suitable scenario for using this loop is when we need to access both the index and the value of each element in an array.
----------------------------------------------------->
Using array.iter().for_each(|x| ...) :
The iter().for_each(|x| ...) method in Rust provides a way to iterate over the elements of an array and apply a closure to each element in a concise and functional style.
Iterating over an array using array.iter().for_each(|x| ...) is equivalent to using a regular for loop, with the difference being that the for_each() method applies a closure to each element of the array.
The iter() method creates an iterator over a reference to the array, which can then be used to apply various iterator methods, such as for_each(). The closure passed to for_each() is executed for each element in the iterator.
Here is an example:
fn main() {
let array = [1, 2, 3, 4, 5];
array.iter().for_each(|x| {
println!("{}", x);
});
}
/*
Op =>
1
2
3
4
5
*/
Using for_each() can be useful when you want to perform a specific operation on each element of an array without having to manually loop over it. However, it is worth noting that using a regular for loop can often be clearer and more concise, especially for simple operations.
Advantages:
- Concise syntax: Using iter().for_each(|x| ...) is a compact and readable way to perform a single operation on each element of an array.
- Functional programming: The use of closures and iterators allows for a more functional programming style, which can make code more readable and easier to reason about.
- Easy to parallelize: The for_each() method allows for parallel execution of the closure on different elements of the array, which can provide performance benefits on multi-core processors.
Disadvantages:
- Limited functionality: The for_each() method only allows for a single operation to be performed on each element of the array. More complex operations may require a different iteration method or additional code.
- No early exit: Unlike a for loop with a break statement, the for_each() method cannot be easily exited early if a certain condition is met. This can be problematic if there is a large array and the operation on each element is expensive.
Suitable scenarios:
- Simple transformations: When you need to perform a simple transformation on each element of an array, such as incrementing each element or converting them to strings, iter().for_each(|x| ...) can be a good choice.
- Parallel execution: When you have a large array and need to perform an operation on each element in parallel, the for_each() method can be a good option.
- Functional programming style: If you are using a functional programming style in your codebase, iter().for_each(|x| ...) can fit well with that approach.
----------------------------------------------------->
In addition to above we can also access the arry using while and loop
Yes, you can use a while loop with an index variable to access the elements of an array:
let array = [1, 2, 3, 4, 5];
let mut index = 0;
while index < array.len() {
? ? println!("{}", array[index]);
? ? index += 1;
}
In this example, we initialize the index variable to 0 and use a while loop to print each element of the array until we have reached the end of the array. We increment the index variable by 1 on each iteration to move to the next element of the array.
You can also use a loop construct to access the elements of an array:
let array = [1, 2, 3, 4, 5];
let mut index = 0;
loop {
? ? if index == array.len() {
? ? ? ? break;
? ? }
? ? println!("{}", array[index]);
? ? index += 1;
}
In this example, we use a loop construct to print each element of the array. We check whether we have reached the end of the array using an if statement and the break keyword to exit the loop. We increment the index variable by 1 on each iteration to move to the next element of the array.
Note that while these loops provide different ways to access the elements of an array, they may have different performance characteristics and may be more or less suitable depending on the specific requirements of your code.
In summary, each of these loops provides a different way to iterate over an array in Rust. The choice of loop depends on the requirements of your specific use case. If you need to modify the elements of the array, you should use the indexing loop or the immutable reference loop. If you only need to read the elements of the array, you should use one of the iterator loops, with the immutable reference loop being the most concise. If you need to consume the array and do not need to use it afterwards, you should use the consuming iterator loop.
Thanks for reading till end .Please comment if you have any comments and suggestions.
Rust accelerationist
1 年Creating a tuple is basically free; so `array.iter().enumerate()` is preferable to `0..array.len()` and indexing – it may even be faster unless you take a full slice of the array first which allows the compiler to hoist the bounds check out of the loop. Also `for x in array.into_iter()` can be written shorter as `for x in array` (because the `into_iter()` is already introduced by the for loop lowering). Finally, you can also use `array.iter().for_each(|x| ...)` to loop over item references. In general prefer what makes the intent clearer; then profile to see where you need performance.