Now its time to take it to the next level
Lets make some things that are a bit... useless, but they show off how to use rust and that is what we are going for
Iterator
An iterator isn't just something we interact with, its something we can also create!
Lets talk about how iterators are implemented (whiteboard)
Lets implement an iterator for Rectangle
. It will iterate over the four
points
src/shapes/rect.rs
- struct
RectIter
with apoints: Vec<(f64, f64)>
andidx: usize
- implement
Iterator
forRectIter
- implement
IntoIterator
forRectangle
src/main.rs
- create a rect
- iterate over a rect
for point in rect
printing out each point - print out the entire rectangle via the
Display
trait
I'll give you a moment to try it a bit yourself
Complete Code
Why are we getting a borrow checker issue?
src/main.rs
use shapes::rect::Rectangle;
mod shapes;
fn main() {
let rect = Rectangle::default();
for point in rect {
println!("({}, {})", point.0, point.1);
}
println!("{}", rect);
}
src/shapes/rect.rs
pub struct RectIter {
points: Vec<(f64, f64)>,
idx: usize,
}
impl Iterator for RectIter {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.points.len() {
return None;
}
let point = self.points[self.idx];
self.idx += 1;
return Some(point);
}
}
impl IntoIterator for Rectangle {
type Item = (f64, f64);
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
return RectIter {
points: vec![
(self.x, self.y),
(self.x + self.width, self.y),
(self.x, self.y + self.height),
(self.x + self.width, self.y + self.height),
],
idx: 0,
}
}
}
IntoIterator
It consumes the thing you give it. We need to give it something to consume that wont consume our original struct!
Lets do a quick example in main with Vec
and a for
loop to show the
consuming vs non consuming.
So what do we do? (make simple fix)
Complete Code
impl IntoIterator for Rectangle {
type Item = (f64, f64);
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
return RectIter {
points: [
(self.x, self.y),
(self.x + self.width, self.y),
(self.x, self.y + self.height),
(self.x + self.width, self.y + self.height),
],
idx: 0,
}
}
}
impl IntoIterator for &Rectangle {
type Item = (f64, f64);
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
return RectIter {
points: [
(self.x, self.y),
(self.x + self.width, self.y),
(self.x, self.y + self.height),
(self.x + self.width, self.y + self.height),
],
idx: 0,
}
}
}
I hate duplicating code
Notice that they are the same code, just different types. One is the value
Rectangle
and the other is a reference to Rectangle
- lets implement a constructor
- lets do it the trait way (
From
)
Complete Code
Never sleep on the From<T>
trait. It allows you to hide complicated code and
it relies on built in behavior.
pub struct RectIter {
points: [(f64, f64); 4],
idx: usize,
}
impl From<&Rectangle> for RectIter {
fn from(rect: &Rectangle) -> Self {
return RectIter {
points: vec![
(rect.x, rect.y),
(rect.x + rect.width, rect.y),
(rect.x, rect.y + rect.height),
(rect.x + rect.width, rect.y + rect.height),
],
idx: 0,
}
}
}
impl IntoIterator for Rectangle {
type Item = (f64, f64);
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
return (&self).into();
}
}
impl IntoIterator for &Rectangle {
type Item = (f64, f64);
type IntoIter = RectIter;
fn into_iter(self) -> Self::IntoIter {
return self.into();
}
}
Questions
We are in some heavy stuff now