
After completing the previous three in-depth articles on Rust iterators, in this article we will attempt to write our own Rust iterator, that is, to add iterator functionality to our own data.
If any readers want to review the previous articles:
- In-Depth Rust Iterators (Part 1)
- In-Depth Rust Iterators (Part 2)
- In-Depth Rust Iterators (Part 3)
Before We Start
Rust’s native <span>Vec</span> and arrays already support iterator-related functionality:
let mut a = vec![1, 2, 3];
for i in &a {
println!("{}", i);
}
a.iter_mut().for_each(|i| {
*i = 100;
});
println!("{:?}", a);
// output
// 1
// 2
// 3
// [100, 100, 100]
If the <span>vec![1, 2, 3]</span> is changed to <span>[1, 2, 3]</span>, the effect is the same.
If your data structure is based on <span>Vec</span> or arrays to implement iterators, you can actually use the iterators of <span>Vec</span> or arrays directly, and there is no need to implement it yourself.
However, to better understand the internal principles of iterators and related implementation details, in this article we will attempt to implement an iterator for our own data.
First, you need a simple class:
#[derive(Debug)]
struct Rect {
a: f32,
b: f32,
c: f32,
d: f32,
}
Implementing an Iterator for the Class
As mentioned in In-Depth Rust Iterators (Part 1), if we want to implement an iterator for our own data, we need to implement the <span>Iterator</span><span> trait</span>.
impl Iterator for Rect {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
//...
}
}
The function that this <span>trait</span> requires to implement is very simple — just <span>next</span>.
<span>next</span> returns an <span>Option</span>, and if the iterator ends, it directly returns <span>None</span>.
To enable iterator functionality, we need to make a slight modification to <span>Rect</span>, adding an iterator index:
#[derive(Debug)]
struct Rect {
a: f32,
b: f32,
c: f32,
d: f32,
iter_index:u8, // Used to count the current iteration position
}
Then, implement the iterator for <span>Rect</span>:
impl Iterator for Rect {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let next = match self.iter_index {
0 => {
Some(self.a)
}
1 => {
Some(self.b)
}
2 => {
Some(self.c)
}
3 => {
Some(self.d)
}
_ => None,
};
self.iter_index += 1;
next
}
}
Now you understand why the parameter required by <span>next</span> is <span>&mut self</span>!
Each time <span>next</span> is called, we first return the current value, then increment the iteration index by <span>+1</span>.
If the iteration ends, it returns <span>None</span>.
let rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
iter_index: 0,
};
for i in rect {
println!("{}", i);
}
// output
// 1
// 2
// 3
// 4
OK, very simple.
However, this implementation has two serious issues:
<span>iter_index</span>greatly affects the iteration; if this value is set incorrectly, the iteration cannot start from the beginning.- It cannot be reused for iteration, meaning you cannot use
<span>for i in rect</span>again.
Let’s look at the first issue; if we set <span>iter_index</span> to <span>3</span>, the result will be:
4
Yes, it will only output a <span>4</span>, because our <span>next</span> implementation is based on <span>iter_index</span>.
The second issue is that when we try to iterate over <span>rect</span> again:

We get an error like this.
The reason is that <span>for i in rect</span> is syntactic sugar provided by Rust, and that code will actually be compiled into the following code:
let mut iter = IntoIterator::into_iter(collection);
loop {
match iter.next() {
Some(i) => {
println!("{}", i);
}
None => break,
}
}
Looking at the source code of <span>IntoIterator::into_iter</span>, we find:
#[inline]
fn into_iter(self) -> I {
self
}
<span>IntoIterator::into_iter</span> takes ownership of the parameter, which is why we cannot write <span>for i in rect</span> twice.
To solve the above two problems, let’s see how a correct iterator is implemented.
IntoIterator
Rust provides a <span>trait</span> — <span>IntoIterator</span> — which serves to convert an “iterable thing” (like a container) into an “iterator”.
Here, our <span>Rect</span> is an iterable data structure, and by implementing <span>IntoIterator</span>, it can become an iterator.
#[derive(Debug)]
struct Rect {
a: f32,
b: f32,
c: f32,
d: f32,
// Removed iter_index here
}
struct RectIter {
data: Rect,
iter_index: u8,
}
impl Iterator for RectIter {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
match self.iter_index {
0 => {
self.iter_index += 1;
Some(self.data.a)
}
1 => {
self.iter_index += 1;
Some(self.data.b)
}
2 => {
self.iter_index += 1;
Some(self.data.c)
}
3 => {
self.iter_index += 1;
Some(self.data.d)
}
_ => None,
}
}
}
impl IntoIterator for Rect {
type Item = f32;
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
RectIter {
data: self,
iter_index: 0,
}
}
}
Readers will find that we have implemented <span>IntoIterator</span> for <span>Rect</span>, which requires returning a <span>IntoIter</span> type, and <span>IntoIter</span> is actually <span>type IntoIter: Iterator<Item = Self::Item>;</span>, which is an iterator.
Here, we define a new iterator type <span>RectIter</span> and implement <span>Iterator</span>, and finally, the <span>into_iter(self)</span> in <span>IntoIterator</span> returns <span>RectIter</span>.
Now, let’s see the effect:
let rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
for i in rect {
println!("{}", i);
}
// output
// 1
// 2
// 3
// 4
We no longer need to define the iteration index in <span>Rect</span>; when using the iterator, the iteration index in <span>RectIter</span> will always start at <span>0</span>.
However, it still does not solve the problem of reusing it, meaning we cannot iterate over <span>Rect</span> again.
Iteration &
What we actually need to do is very simple: provide an iterator for <span>&Rect</span>, so we can iterate over <span>&Rect</span>.
// Create an iterator structure for &Rect (borrowed type)
struct RectRefIterator<'a> {
rect: &'a Rect,
index: usize,
}
// Implement Iterator trait for RectRefIterator
impl<'a> Iterator for RectRefIterator<'a> {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let result = match self.index {
0 => Some(self.rect.a),
1 => Some(self.rect.b),
2 => Some(self.rect.c),
3 => Some(self.rect.d),
_ => None,
};
self.index += 1;
result
}
}
// Implement IntoIterator trait for &Rect (borrowed type)
impl<'a> IntoIterator for &'a Rect {
type Item = f32;
type IntoIter = RectRefIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
RectRefIterator {
rect: self,
index: 0,
}
}
}
We directly define the implementation of <span>&Rect</span> for <span>IntoIterator</span>, and the syntax here is a bit long:<span>impl<'a> IntoIterator for &'a Rect</span>.
<span>'a</span> is a lifetime annotation; if you are not familiar with lifetime annotations, you can skip this for now. We can simply understand it as needing to help the compiler determine how long this reference can live if you have a reference type.
At this point, the returned <span>IntoIter</span> type requires us to redefine a <span>RectRefIterator<'a></span>, which will hold a <span>&'a Rect</span>, that is, a <span>&Rect</span> type.
Thus, we can iterate multiple times.
let rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
for i in &rect { // Note that this is &rect
println!("{}", i);
}
for i in &rect {
println!("{}", i);
}
for i in &rect {
println!("{}", i);
}
If you want to consume <span>rect</span>, just use <span>for i in rect</span>.
Now, what if I want to support <span>&mut</span>?
Iteration &mut
<span>for i in &mut rect</span> generally indicates that I want to modify elements during iteration, for example, the following modifies values inside an array during iteration:
for i in &mut a {
*i = 100;
}
Alright, this should be the most complicated one to write among all iterators.
Let’s try to write an iterator according to the above syntax:
struct RectMutRefIterator<'a> {
rect: &'a mut Rect,
index: usize,
}
impl<'a> Iterator for RectMutRefIterator<'a> {
type Item = &'a mut f32;
fn next(&mut self) -> Option<Self::Item> {
let index = self.index;
self.index += 1;
match index {
0 => Some(&mut self.rect.a),
1 => Some(&mut self.rect.b),
2 => Some(&mut self.rect.c),
3 => Some(&mut self.rect.d),
_ => None,
}
}
}
Actually, it just changes <span>&Rect</span> to <span>&mut Rect</span>.
However, this code will not compile; you will get an error message like this:

Here’s a brief explanation: <span>fn next(&mut self) -> Option<Self::Item></span> is actually <span>fn next(&'a mut self)</span>, and this function needs to return a reference with a <span>'a</span> lifetime, but the Rust compiler does not know where this <span>'a</span> comes from; the lifetime annotations of Rust functions must have a source.
What if we change it to <span>fn next(&'a mut self)</span>?
That won’t work either, because <span>impl<'a> Iterator</span> function definitions are not like that, and at this point, it seems we are stuck…
Due to the complexity of this part, we will not delve deeper; I will provide one correct answer:
// ----------------------------------------------------
// 1. Custom iterator structure
// ----------------------------------------------------
pub struct RectMutIterator<'a> {
// Raw mutable pointer to the current f32 element inside Rect.
ptr: *mut f32,
// Remaining number of elements.
len: usize,
// Lifetime marker: binds the iterator's lifetime 'a to the &mut borrow of Rect.
_marker: PhantomData<&'a mut f32>,
}
// ----------------------------------------------------
// 2. Implement IntoIterator for &mut Rect
// ----------------------------------------------------
impl<'a> IntoIterator for &'a mut Rect {
type Item = &'a mut f32;
type IntoIter = RectMutIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
let ptr_f32 = self as *mut Rect as *mut f32;
RectMutIterator {
ptr: ptr_f32,
len: 4, // Total number of fields
_marker: PhantomData,
}
}
}
// ----------------------------------------------------
// 3. Implementing the iterator
// ----------------------------------------------------
impl<'a> Iterator for RectMutIterator<'a> {
type Item = &'a mut f32; // Returns mutable reference to f32
fn next(&mut self) -> Option<Self::Item> {
// 1. Check if iteration is complete
if self.len == 0 {
return None;
}
// 2. Decrease the count of remaining elements
self.len -= 1;
// 3. Core unsafe logic
unsafe {
// Get the pointer to the current element
let current_ptr = self.ptr;
// Move the internal pointer to the next element
// offset(1) is a safe abstraction, but it performs unsafe pointer arithmetic internally
// It ensures that `self.ptr` now points to the next f32 (i.e., the next field)
self.ptr = self.ptr.offset(1);
// 4. Convert the current raw pointer back to a safe &mut f32 reference
// This is safe because we manage the current element through len and ptr,
// ensuring that the current element is exclusive and its lifetime is correctly bound.
Some(&mut *current_ptr)
}
}
}
Let’s test it:
let mut rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
for i in &mut rect {
*i += 10.0;
}
println!("{:?}", rect);
// output
// Rect { a: 11.0, b: 12.0, c: 13.0, d: 14.0 }
The result is correct.
Alright, we have implemented several basic iterator types. Except for <span>&mut</span>, which is relatively complex, the others are quite straightforward.
Some Rust Conventions
In fact, there are some conventions regarding iterators in Rust. In In-Depth Rust Iterators (Part 1), we mentioned that there are three equivalent cases in Rust iterators:
<span>for i in &vec</span>is equivalent to<span>for i in vec.iter()</span>.<span>for i in &mut</span>is equivalent to<span>for i in vec.iter_mut()</span>.<span>for i in vec</span>is equivalent to<span>for i in vec.into_iter()</span>.
Therefore, we will add these three methods to <span>Rect</span>:
impl Rect {
fn iter_mut(&mut self) -> RectMutIterator {
RectMutIterator {
ptr: self as *mut Rect as *mut f32,
len: 4,
_marker: PhantomData,
}
}
fn iter(&self) -> RectRefIterator {
RectRefIterator {
rect: self,
index: 0,
}
}
fn into_iter(self) -> RectIter {
RectIter {
data: self,
iter_index: 0,
}
}
}
Now we can use iterator adapter methods and pipelines:
let mut rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
rect.iter_mut().for_each(|i| {
*i += 10.0;
});
rect.iter().for_each(|a| print!("{:?} ", a));
println!();
rect.into_iter().for_each(|a| print!("{:?} ", a));
println!();
// output
// 11.0 12.0 13.0 14.0
// 11.0 12.0 13.0 14.0
Reversing
In In-Depth Rust Iterators (Part 3), we finally mentioned a <span>rev()</span><span>, which is the reverse iteration of the iterator. Here we will also implement it so you can understand why the output results were like that in that article.</span>
We will only implement reverse iteration for <span>&Rect</span>.
To implement reverse iteration, we need to add an <span>end</span> field to <span>RectRefIterator</span>, which serves the same purpose as <span>index</span>, indicating which data should be returned currently.
struct RectRefIterator<'a> {
rect: &'a Rect,
index: usize,
end: usize,
}
Next, in all places where <span>RectRefIterator</span> is used, set <span>end</span> to <span>4</span>:
RectRefIterator {
rect: self,
index: 0,
end: 4,
}
To allow the iterator to support <span>rev()</span><span>, we need to implement a </span><code><span>DoubleEndedIterator</span> trait, which only requires implementing <span>fn next_back(&mut self)</span><span>, which returns data in reverse.</span>
impl<'a> DoubleEndedIterator for RectRefIterator<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let result = match self.end {
1 => Some(self.rect.a),
2 => Some(self.rect.b),
3 => Some(self.rect.c),
4 => Some(self.rect.d),
_ => return None,
};
self.end -= 1;
result
}
}
Alright, everything is ready; let’s test it:
let mut rect = Rect {
a: 1.0,
b: 2.0,
c: 3.0,
d: 4.0,
};
rect.iter().rev().for_each(|a| print!("{:?} ", a));
// output
// 4.0 3.0 2.0 1.0
Perfect.
Conclusion
Alright, this series on Rust iterators has finally come to an end.
In this final article, we implemented iterator functionality for our own data.
By manually writing iterators, we not only learned how to implement iterators but also deepened our understanding of data usage in Rust. You will find that Rust always revolves around ownership, borrowing, and mutable borrowing to achieve data access.
This design is not a limitation but a guidance — it forces us to think while writing code:
- Who owns the data?
- Who is reading or modifying it?
- Is the lifetime safe?
It is precisely these seemingly “strict” rules that allow Rust to ensure memory safety without relying on garbage collection while achieving zero-cost abstraction. And iterators are a perfect embodiment of this philosophy: they hand control to the developer while the type system silently guards the safety of every reference.
Now, when you see <span>for x in vec</span><span>, you may no longer just find it concise and elegant, but also appreciate the intricate, reliable, and intelligent mechanisms that operate silently behind the scenes.</span>
Thank you for making it this far. May you continue to enjoy this “constrained freedom” on your journey with Rust.