Repository: ManevilleF/hexx (rust)

Issue:
Return `ExactSizeIterator` from shapes where possible
https://github.com/ManevilleF/hexx/blob/9e7dc161e219e8d3147b4b1d3fc461504c14fe62/src/lib.rs#L156

This avoids needing to slowly count the iterator. This was particularly silly because iterators are consumed by doing so.

See this code that I wrote:

```rust
    /// Returns the average height of tiles around `tile_pos` within `radius`
    pub(crate) fn average_height(&self, tile_pos: TilePos, radius: u32) -> f32 {
        let hex_iter = hexagon(tile_pos.hex, radius);
        let n = hex_iter.count();
        let hex_iter = hexagon(tile_pos.hex, radius);
        let heights = hex_iter.map(|hex| *self.height_index.get(&TilePos { hex }).unwrap_or(&0.));
        heights.sum::<f32>() / n as f32
    }
```

Pull request:
ExactSizeIterator
> Closes #46 

# Work Done

- [x] Added a private iterator wrapper `ExactSizeHexIterator` 
- [x] Wrapped known size iterator methods in the wrapper:
  - [x] `Hex::range`
  - [x] `Hex::line_to`
  - [x] `shapes::hexagon`  
  - [x] `HexBounds::all_coords` 
 - [x] Extend the same for rings
   - [x] ring edges 
   - [x] Added *full wedges* with exact size 
 - [x] Extend the same for other shapes where applicable

Patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 6fa1052..1b62ba5 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -67,4 +67,4 @@ jobs:
       - uses: actions/checkout@v3
       - uses: dtolnay/rust-toolchain@stable
       - name: rustdoc
-        run: cargo rustdoc --all-features
+        run: cargo rustdoc --all-features -- -D warnings
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72d7b52..d142f4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,34 @@
 
 ## Unreleased
 
+### ExactSizeIterator
+
+* `Hex::range` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `Hex::line_to` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `Hex::wedge_to` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `Hex::custom_wedge_to` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `HexBounds::all_coords` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `shapes::hexagon` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `shapes::parallelogram` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `shapes::triangle` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `shapes::pointy_rectangle` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+* `shapes::flat_rectangle` now returns a `ExactSizeIterator` instead of a simple `Iterator`
+
+* (**BREAKING**) `Hex::ring_edge` now returns a `ExactSizeIterator` instead of a `Vec`
+* (**BREAKING**) `Hex::custom_ring_edge` now returns a `ExactSizeIterator` instead of a `Vec`
+* (**BREAKING**) `Hex::custom_ring_edges` now returns an `Iterator` with an `ExactSizeIterator` item instead of a `Vec`
+* (**BREAKING**) `Hex::ring_edges` now returns an `Iterator` with an `ExactSizeIterator` item instead of a `Vec`
+
+### Additions 
+
+* Added `Hex::wedge_count` to count the amount of coordinate in a wedge
+* Added `Hex::full_wedge` which returns an `ExactSizeIterator`
+* Added `Hex::custom_full_wedge` which returns an `ExactSizeIterator`
+
+### Miscellaneous
+
 * Deprecated `Hex::to_array3` in favor of `Hex::to_cubic_array`
+* Made most modules public but kept the wildcard exports at the crate root
 
 ## 0.4.2
 
diff --git a/src/bounds.rs b/src/bounds.rs
index e3cd865..57ebb41 100644
--- a/src/bounds.rs
+++ b/src/bounds.rs
@@ -36,14 +36,17 @@ impl HexBounds {
 
     #[must_use]
     #[inline]
+    #[doc(alias = "coords_count")]
+    #[doc(alias = "len")]
     /// Returns the number of hexagons in bounds
     pub const fn hex_count(&self) -> usize {
         Hex::range_count(self.radius)
     }
 
     #[doc(alias = "all_items")]
+    #[must_use]
     /// Returns an iterator with all the coordinates in bounds
-    pub fn all_coords(&self) -> impl Iterator<Item = Hex> {
+    pub fn all_coords(&self) -> impl ExactSizeIterator<Item = Hex> {
         self.center.range(self.radius)
     }
 
diff --git a/src/direction/mod.rs b/src/direction/mod.rs
index 6c6f625..b82e206 100644
--- a/src/direction/mod.rs
+++ b/src/direction/mod.rs
@@ -1,6 +1,10 @@
 /// [`Hex`] diagonal directions
+///
+/// [`Hex`]: crate::Hex
 mod diagonal_direction;
 /// [`Hex`] neighbor directions
+///
+/// [`Hex`]: crate::Hex
 mod hex_direction;
 /// Trait implementations
 mod impls;
diff --git a/src/hex/iter.rs b/src/hex/iter.rs
index 4462def..5dbd68f 100644
--- a/src/hex/iter.rs
+++ b/src/hex/iter.rs
@@ -66,3 +66,30 @@ impl<I: Iterator<Item = Hex>> HexIterExt for I {
         self.collect()
     }
 }
+
+/// Private container for a [`Hex`] [`Iterator`] of known size
+#[derive(Debug, Clone)]
+pub struct ExactSizeHexIterator<I> {
+    /// The inner iterator
+    pub iter: I,
+    /// The remaining iterator elements count
+    pub count: usize,
+}
+
+impl<I> Iterator for ExactSizeHexIterator<I>
+where
+    I: Iterator<Item = Hex>,
+{
+    type Item = Hex;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.count = self.count.saturating_sub(1);
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (self.count, Some(self.count))
+    }
+}
+
+impl<I> ExactSizeIterator for ExactSizeHexIterator<I> where I: Iterator<Item = Hex> {}
diff --git a/src/hex/mod.rs b/src/hex/mod.rs
index b046472..1ac47a0 100644
--- a/src/hex/mod.rs
+++ b/src/hex/mod.rs
@@ -9,6 +9,7 @@ mod rings;
 #[cfg(test)]
 mod tests;
 
+pub(crate) use iter::ExactSizeHexIterator;
 pub use iter::HexIterExt;
 
 use crate::{DiagonalDirection, Direction};
@@ -716,6 +717,7 @@ impl Hex {
     }
 
     #[allow(clippy::cast_precision_loss)]
+    #[must_use]
     /// Computes all coordinates in a line from `self` to `other`.
     ///
     /// # Example
@@ -724,14 +726,19 @@ impl Hex {
     /// let start = Hex::ZERO;
     /// let end = Hex::new(5, 0);
     ///
-    /// let line: Vec<Hex> = start.line_to(end).collect();
+    /// let line = start.line_to(end);
+    /// assert_eq!(line.len(), 6);
+    /// let line: Vec<Hex> = line.collect();
     /// assert_eq!(line.len(), 6);
     /// ````
-    pub fn line_to(self, other: Self) -> impl Iterator<Item = Self> {
-        let distance = self.distance_to(other);
+    pub fn line_to(self, other: Self) -> impl ExactSizeIterator<Item = Self> {
+        let distance = self.unsigned_distance_to(other);
         let dist = distance.max(1) as f32;
         let [a, b]: [Vec2; 2] = [self.as_vec2(), other.as_vec2()];
-        (0..=distance).map(move |step| a.lerp(b, step as f32 / dist).into())
+        ExactSizeHexIterator {
+            iter: (0..=distance).map(move |step| a.lerp(b, step as f32 / dist).into()),
+            count: distance as usize + 1,
+        }
     }
 
     /// Performs a linear interpolation between `self` and `rhs` based on the value `s`.
@@ -748,12 +755,18 @@ impl Hex {
     }
 
     #[allow(clippy::cast_possible_wrap)]
-    /// Retrieves all [`Hex`] around `self` in a given `range`
-    pub fn range(self, range: u32) -> impl Iterator<Item = Self> {
-        let range = range as i32;
-        (-range..=range).flat_map(move |x| {
-            (max(-range, -x - range)..=min(range, range - x)).map(move |y| self + Self::new(x, y))
-        })
+    #[must_use]
+    /// Retrieves all [`Hex`] around `self` in a given `range`.
+    /// The number of returned coordinates is equal to `Hex::range_count(range)`
+    pub fn range(self, range: u32) -> impl ExactSizeIterator<Item = Self> {
+        let radius = range as i32;
+        ExactSizeHexIterator {
+            iter: (-radius..=radius).flat_map(move |x| {
+                (max(-radius, -x - radius)..=min(radius, radius - x))
+                    .map(move |y| self + Self::new(x, y))
+            }),
+            count: Self::range_count(range),
+        }
     }
 
     #[inline]
diff --git a/src/hex/rings.rs b/src/hex/rings.rs
index 67f53d6..ac187af 100644
--- a/src/hex/rings.rs
+++ b/src/hex/rings.rs
@@ -1,4 +1,4 @@
-use super::{Direction, Hex};
+use super::{iter::ExactSizeHexIterator, Direction, Hex};
 use crate::DiagonalDirection;
 
 impl Hex {
@@ -70,7 +70,7 @@ impl Hex {
     }
 
     /// Retrieves `range` [`Hex`] rings around `self` in a given `range`.
-    /// The returned coordinates start from [`start_dir`] and loop around `self` counter clockwise
+    /// The returned coordinates start from `start_dir` and loop around `self` counter clockwise
     /// unless `clockwise` is set to true.
     ///
     /// If you only need the coordinates see [`Self::spiral_range`] or [`Self::rings`].
@@ -96,7 +96,7 @@ impl Hex {
 
     #[must_use]
     #[allow(clippy::cast_possible_wrap)]
-    /// Retrieves one [`Hex`] ring edge around `self` in a given `range` and `direction`.
+    /// Retrieves one [`Hex`] ring edge around `self` in a given `radius` and `direction`.
     /// The returned coordinates are sorted counter clockwise unless `clockwise` is set to `true`.
     ///
     /// If you only need the coordinates see [`Self::ring_edge`].
@@ -105,13 +105,10 @@ impl Hex {
     /// The returned vector will be of `radius + 1` length
     pub fn custom_ring_edge(
         self,
-        range: u32,
+        radius: u32,
         direction: DiagonalDirection,
         clockwise: bool,
-    ) -> Vec<Self> {
-        if range == 0 {
-            return vec![self];
-        }
+    ) -> impl ExactSizeIterator<Item = Self> {
         let [start_dir, end_dir] = if clockwise {
             let dir = direction.direction_left();
             [dir, dir + 2]
@@ -120,23 +117,30 @@ impl Hex {
             [dir, dir - 2]
         };
         let end_dir = Self::neighbor_coord(end_dir);
-        let hex = self + Self::neighbor_coord(start_dir) * range as i32;
-        (0..=range).map(|i| hex + end_dir * i as i32).collect()
+        let hex = self + Self::neighbor_coord(start_dir) * radius as i32;
+        ExactSizeHexIterator {
+            iter: (0..=radius).map(move |i| hex + end_dir * i as i32),
+            count: radius as usize + 1,
+        }
     }
 
     #[must_use]
-    /// Retrieves one [`Hex`] ring edge around `self` in a given `range` and `direction`.
+    /// Retrieves one [`Hex`] ring edge around `self` in a given `radius` and `direction`.
     /// The returned coordinates are sorted counter clockwise around `self`.
     ///
     /// See [`Self::custom_ring_edge`] for more options.
     ///
     /// # Note
     /// The returned vector will be of `radius + 1` length
-    pub fn ring_edge(self, range: u32, direction: DiagonalDirection) -> Vec<Self> {
-        self.custom_ring_edge(range, direction, false)
+    pub fn ring_edge(
+        self,
+        radius: u32,
+        direction: DiagonalDirection,
+    ) -> impl ExactSizeIterator<Item = Self> {
+        self.custom_ring_edge(radius, direction, false)
     }
 
-    /// Retrieves all successive [`Hex`] ring edges around `self` in a given `range` and
+    /// Retrieves all successive [`Hex`] ring edges around `self` in given `ranges` and
     /// `direction`.
     /// The returned edges coordinates are sorted counter clockwise around `self`.
     ///
@@ -152,13 +156,13 @@ impl Hex {
     /// If you only need the coordinates see [`Self::custom_wedge`]
     pub fn ring_edges(
         self,
-        range: impl Iterator<Item = u32>,
+        ranges: impl Iterator<Item = u32>,
         direction: DiagonalDirection,
-    ) -> impl Iterator<Item = Vec<Self>> {
-        range.map(move |r| self.ring_edge(r, direction))
+    ) -> impl Iterator<Item = impl ExactSizeIterator<Item = Self>> {
+        ranges.map(move |r| self.ring_edge(r, direction))
     }
 
-    /// Retrieves all successive [`Hex`] ring edges around `self` in a given `range` and
+    /// Retrieves all successive [`Hex`] ring edges around `self` in given `ranges` and
     /// `direction`.
     /// The returned edges coordinates are sorted counter clockwise around `self` unless
     /// `clockwise` is set to `true`.
@@ -175,27 +179,28 @@ impl Hex {
     /// If you only need the coordinates see [`Self::wedge`]
     pub fn custom_ring_edges(
         self,
-        range: impl Iterator<Item = u32>,
+        ranges: impl Iterator<Item = u32>,
         direction: DiagonalDirection,
         clockwise: bool,
-    ) -> impl Iterator<Item = Vec<Self>> {
-        range.map(move |r| self.custom_ring_edge(r, direction, clockwise))
+    ) -> impl Iterator<Item = impl ExactSizeIterator<Item = Self>> {
+        ranges.map(move |r| self.custom_ring_edge(r, direction, clockwise))
     }
 
-    /// Retrieves all successive [`Hex`] ring edges around `self` in a given `range` and
+    /// Retrieves all successive [`Hex`] ring edges around `self` in given `ranges` and
     /// `direction`.
     /// The returned edges coordinates are sorted counter clockwise around `self` unless
     /// `clockwise` is set to `true`.
     ///
     /// See also [`Self::custom_ring_edges`]
     /// If you only need the coordinates see [`Self::wedge`]
+    /// If you want a full wedge see [`Self::custom_full_wedge`]
     pub fn custom_wedge(
         self,
-        range: impl Iterator<Item = u32>,
+        ranges: impl Iterator<Item = u32>,
         direction: DiagonalDirection,
         clockwise: bool,
     ) -> impl Iterator<Item = Self> {
-        self.custom_ring_edges(range, direction, clockwise)
+        self.custom_ring_edges(ranges, direction, clockwise)
             .flatten()
     }
 
@@ -204,10 +209,49 @@ impl Hex {
     /// `clockwise` is set to `true`.
     ///
     /// See also [`Self::custom_ring_edges`] and [`Self::wedge_to`]
-    pub fn custom_wedge_to(self, rhs: Self, clockwise: bool) -> impl Iterator<Item = Self> {
+    #[must_use]
+    pub fn custom_wedge_to(
+        self,
+        rhs: Self,
+        clockwise: bool,
+    ) -> impl ExactSizeIterator<Item = Self> {
         let range = self.unsigned_distance_to(rhs);
         let direction = self.diagonal_to(rhs);
-        self.custom_wedge(0..=range, direction, clockwise)
+        self.custom_full_wedge(range, direction, clockwise)
+    }
+
+    /// Retrieves all successive [`Hex`] ring edges around `self` in a given `range` and `direction`
+    /// The returned edges coordinates are sorted counter clockwise around `self` unless
+    /// `clockwise` is set to `true`.
+    ///
+    /// See also [`Self::custom_wedge`] and [`Self::full_wedge`]
+    #[must_use]
+    pub fn custom_full_wedge(
+        self,
+        range: u32,
+        direction: DiagonalDirection,
+        clockwise: bool,
+    ) -> impl ExactSizeIterator<Item = Self> {
+        ExactSizeHexIterator {
+            iter: self.custom_wedge(0..=range, direction, clockwise),
+            count: Self::wedge_count(range) as usize,
+        }
+    }
+
+    /// Counts how many coordinates there are in a wedge of given `range`
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// # use hexx::*;
+    /// let hex = Hex::new(3, -6);
+    /// let wedge: Vec<Hex> = hex.wedge(0..=13, DiagonalDirection::Right).collect();
+    /// assert_eq!(wedge.len(), Hex::wedge_count(13) as usize);
+    /// ```
+    #[inline]
+    #[must_use]
+    pub const fn wedge_count(range: u32) -> u32 {
+        range * (range + 3) / 2 + 1
     }
 
     /// Retrieves all successive [`Hex`] ring edges around `self` in a given `range` and
@@ -227,14 +271,28 @@ impl Hex {
     /// The returned edges coordinates are sorted counter clockwise.
     ///
     /// See also [`Self::custom_ring_edges`] and [`Self::custom_wedge_to`]
-    pub fn wedge_to(self, rhs: Self) -> impl Iterator<Item = Self> {
+    #[must_use]
+    pub fn wedge_to(self, rhs: Self) -> impl ExactSizeIterator<Item = Self> {
         self.custom_wedge_to(rhs, false)
     }
 
+    /// Retrieves all successive [`Hex`] ring edges around `self` in a given `range` and `direction`
+    /// The returned edges coordinates are sorted counter clockwise around `self`.
+    ///
+    /// See also [`Self::custom_full_wedge`] and [`Self::wedge`]
+    #[must_use]
+    pub fn full_wedge(
+        self,
+        range: u32,
+        direction: DiagonalDirection,
+    ) -> impl ExactSizeIterator<Item = Self> {
+        self.custom_full_wedge(range, direction, false)
+    }
+
     /// Retrieves all successive [`Hex`] half ring edges around `self` in a given `range` and
     /// `direction`.
     ///
-    /// See also [`Self::wedge_dir_to`] and [`Self::wedge`]
+    /// See also [`Self::corner_wedge_to`] and [`Self::wedge`]
     pub fn corner_wedge(
         self,
         range: impl Iterator<Item = u32> + Clone,
@@ -248,7 +306,7 @@ impl Hex {
 
     /// Retrieves all successive [`Hex`] half ring edges from `self` to `rhs`
     ///
-    /// See also [`Self::wedge_dir`] and [`Self::wedge_to`]
+    /// See also [`Self::corner_wedge_to`] and [`Self::wedge_to`]
     pub fn corner_wedge_to(self, rhs: Self) -> impl Iterator<Item = Self> {
         let range = self.unsigned_distance_to(rhs);
         self.corner_wedge(0..=range, self.direction_to(rhs))
@@ -261,7 +319,7 @@ impl Hex {
     /// The returned edges coordinates are sorted counter clockwise around `self` unless
     /// `clockwise` is set to `true`.
     ///
-    /// See also [`Self::custom_cached_ring_edges`]
+    /// See also [`Self::cached_ring_edges`]
     /// If you only need the coordinates see [`Self::ring_edges`] or [`Self::wedge`].
     ///
     /// # Usage
@@ -289,7 +347,10 @@ impl Hex {
         direction: DiagonalDirection,
         clockwise: bool,
     ) -> [Vec<Self>; RANGE] {
-        std::array::from_fn(|r| self.custom_ring_edge(r as u32, direction, clockwise))
+        std::array::from_fn(|r| {
+            self.custom_ring_edge(r as u32, direction, clockwise)
+                .collect()
+        })
     }
 
     #[allow(clippy::cast_possible_truncation)]
@@ -298,7 +359,7 @@ impl Hex {
     /// `direction` as an array of edges.
     /// The returned edges coordinates are sorted counter clockwise around `self`.
     ///
-    /// See also [`Self::custom_cached_ring_edges`]
+    /// See also [`Self::cached_custom_ring_edges`]
     /// If you only need the coordinates see [`Self::ring_edges`] or [`Self::wedge`].
     ///
     /// # Usage
@@ -325,7 +386,7 @@ impl Hex {
         self,
         direction: DiagonalDirection,
     ) -> [Vec<Self>; RANGE] {
-        std::array::from_fn(|r| self.ring_edge(r as u32, direction))
+        std::array::from_fn(|r| self.ring_edge(r as u32, direction).collect())
     }
 
     #[allow(clippy::cast_possible_truncation)]
@@ -334,7 +395,7 @@ impl Hex {
     /// rings.
     /// The returned rings start from [`Direction::TopRight`] and loop around `self` counter clockwise.
     ///
-    /// See also [`Self::custom_cached_rings`]
+    /// See also [`Self::cached_custom_rings`]
     /// If you only need the coordinates see [`Self::range`] or [`Self::spiral_range`].
     ///
     /// # Usage
diff --git a/src/lib.rs b/src/lib.rs
index c1b761f..a9e6daf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -118,58 +118,27 @@
 #![forbid(unsafe_code)]
 #![warn(clippy::nursery, clippy::pedantic, clippy::cargo, missing_docs)]
 #![allow(clippy::default_trait_access, clippy::module_name_repetitions)]
-mod bounds;
-mod conversions;
-mod direction;
-mod hex;
-mod hex_map;
-mod layout;
-mod mesh;
-mod orientation;
+/// Hexagonal range bounds module
+pub mod bounds;
+/// Hexagonal coordinates conversion module
+pub mod conversions;
+/// Hexagonal directions module
+pub mod direction;
+/// Hexagonal coordinates module
+pub mod hex;
+/// Wraparound hex grid module
+pub mod hex_map;
+/// Hexagonal layout module
+pub mod layout;
+/// Mesh generation utils module
+pub mod mesh;
+/// Hexagon oritentation module
+pub mod orientation;
+/// Map shapes generation functions
+pub mod shapes;
 
 /// Used glam types reexport
 pub use glam::{IVec2, Vec2};
 pub use {
     bounds::*, conversions::*, direction::*, hex::*, hex_map::*, layout::*, mesh::*, orientation::*,
 };
-
-/// Map shapes generation functions
-pub mod shapes {
-    use super::Hex;
-
-    /// Generates a parallelogram layout from `min` to `max`
-    pub fn parallelogram(min: Hex, max: Hex) -> impl Iterator<Item = Hex> {
-        (min.x()..=max.x()).flat_map(move |x| (min.y()..=max.y()).map(move |y| Hex::new(x, y)))
-    }
-
-    /// Generates a triangle with a custom `size`
-    ///
-    /// # Note
-    ///
-    /// To offset the map, apply the offset to each `Item` of the returned iterator
-    #[allow(clippy::cast_possible_wrap)]
-    pub fn triangle(size: u32) -> impl Iterator<Item = Hex> {
-        (0..=size).flat_map(move |x| (0..=(size - x)).map(move |y| Hex::new(x as i32, y as i32)))
-    }
-
-    /// Generates an hexagonal layout around `center` with a custom `radius`.
-    pub fn hexagon(center: Hex, radius: u32) -> impl Iterator<Item = Hex> {
-        center.range(radius)
-    }
-
-    /// Generates a rectangle with the given bounds for "pointy topped" hexagons
-    pub fn pointy_rectangle([left, right, top, bottom]: [i32; 4]) -> impl Iterator<Item = Hex> {
-        (top..=bottom).flat_map(move |y| {
-            let y_offset = y >> 1;
-            ((left - y_offset)..=(right - y_offset)).map(move |x| Hex::new(x, y))
-        })
-    }
-
-    /// Generates a rectangle with the given bounds for "flat topped" hexagons
-    pub fn flat_rectangle([left, right, top, bottom]: [i32; 4]) -> impl Iterator<Item = Hex> {
-        (left..=right).flat_map(move |x| {
-            let x_offset = x >> 1;
-            ((top - x_offset)..=(bottom - x_offset)).map(move |y| Hex::new(x, y))
-        })
-    }
-}
diff --git a/src/shapes.rs b/src/shapes.rs
new file mode 100644
index 0000000..cffa520
--- /dev/null
+++ b/src/shapes.rs
@@ -0,0 +1,132 @@
+use crate::{hex::ExactSizeHexIterator, Hex};
+
+/// Generates a parallelogram layout from `min` to `max`
+#[must_use]
+#[allow(clippy::cast_sign_loss)]
+pub fn parallelogram(min: Hex, max: Hex) -> impl ExactSizeIterator<Item = Hex> {
+    let dist = (max.x.saturating_sub(min.x) + 1) * (max.y.saturating_sub(min.y) + 1);
+    ExactSizeHexIterator {
+        iter: (min.x()..=max.x())
+            .flat_map(move |x| (min.y()..=max.y()).map(move |y| Hex::new(x, y))),
+        count: dist as usize,
+    }
+}
+
+/// Generates a triangle with a custom `size`
+///
+/// # Note
+///
+/// To offset the map, apply the offset to each `Item` of the returned iterator
+#[allow(clippy::cast_possible_wrap)]
+#[must_use]
+pub fn triangle(size: u32) -> impl ExactSizeIterator<Item = Hex> {
+    ExactSizeHexIterator {
+        iter: (0..=size)
+            .flat_map(move |x| (0..=(size - x)).map(move |y| Hex::new(x as i32, y as i32))),
+        count: Hex::wedge_count(size) as usize,
+    }
+}
+
+/// Generates an hexagonal layout around `center` with a custom `radius`.
+#[must_use]
+pub fn hexagon(center: Hex, radius: u32) -> impl ExactSizeIterator<Item = Hex> {
+    center.range(radius)
+}
+
+/// Generates a rectangle with the given bounds for "pointy topped" hexagons.
+///
+/// The function takes four offsets as `[left, right, top, bottom]`.
+/// The generations goes `left` to `right` and `top` to `bottom`, therefore `right` must be greater
+/// than `left` and `bottom` must be greater than `top`.
+#[must_use]
+#[allow(clippy::cast_sign_loss)]
+pub fn pointy_rectangle(
+    [left, right, top, bottom]: [i32; 4],
+) -> impl ExactSizeIterator<Item = Hex> {
+    let count = (right.saturating_sub(left) + 1) * (bottom.saturating_sub(top) + 1);
+    ExactSizeHexIterator {
+        iter: (top..=bottom).flat_map(move |y| {
+            let y_offset = y >> 1;
+            ((left - y_offset)..=(right - y_offset)).map(move |x| Hex::new(x, y))
+        }),
+        count: count as usize,
+    }
+}
+
+/// Generates a rectangle with the given bounds for "flat topped" hexagons
+///
+/// The function takes four offsets as `[left, right, top, bottom]`.
+/// The generations goes `left` to `right` and `top` to `bottom`, therefore `right` must be greater
+/// than `left` and `bottom` must be greater than `top`.
+#[must_use]
+#[allow(clippy::cast_sign_loss)]
+pub fn flat_rectangle([left, right, top, bottom]: [i32; 4]) -> impl ExactSizeIterator<Item = Hex> {
+    let count = (right.saturating_sub(left) + 1) * (bottom.saturating_sub(top) + 1);
+    ExactSizeHexIterator {
+        iter: (left..=right).flat_map(move |x| {
+            let x_offset = x >> 1;
+            ((top - x_offset)..=(bottom - x_offset)).map(move |y| Hex::new(x, y))
+        }),
+        count: count as usize,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn hexagon_test() {
+        let hex = Hex::new(3, -987);
+        for range in 0..=30 {
+            let iter = hexagon(hex, range);
+            assert_eq!(iter.len(), iter.count());
+        }
+    }
+
+    #[test]
+    fn triangle_test() {
+        for range in 0..=30 {
+            let iter = triangle(range);
+            assert_eq!(iter.len(), iter.count());
+        }
+    }
+
+    #[test]
+    fn parallelogram_test() {
+        for min in 0..=30 {
+            for max in 0..=30 {
+                let iter = parallelogram(Hex::splat(min), Hex::splat(min + max));
+                assert_eq!(iter.len(), iter.count());
+            }
+        }
+    }
+
+    #[test]
+    fn pointy_rectangle_test() {
+        for left in -20..=20 {
+            for right in 0..=20 {
+                for top in -20..=20 {
+                    for bottom in 0..=20 {
+                        let iter = pointy_rectangle([left, left + right, top, top + bottom]);
+                        assert_eq!(iter.len(), iter.count());
+                    }
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn flat_rectangle_test() {
+        for left in -20..=20 {
+            for right in 0..=20 {
+                for top in -20..=20 {
+                    for bottom in 0..=20 {
+                        let iter = flat_rectangle([left, left + right, top, top + bottom]);
+                        assert_eq!(iter.len(), iter.count());
+                    }
+                }
+            }
+        }
+    }
+}


Test patch:
diff --git a/src/hex/tests.rs b/src/hex/tests.rs
index dfce358..a1c81d5 100644
--- a/src/hex/tests.rs
+++ b/src/hex/tests.rs
@@ -307,8 +307,12 @@ fn lerp() {
 fn line_to() {
     let a = Hex::new(0, 0);
     let b = Hex::new(5, 0);
+    let line = a.line_to(b);
+    let len = line.len();
+    let line: Vec<_> = line.collect();
+    assert_eq!(line.len(), len);
     assert_eq!(
-        a.line_to(b).collect::<Vec<_>>(),
+        line,
         vec![
             a,
             Hex::new(1, 0),
@@ -319,8 +323,12 @@ fn line_to() {
         ]
     );
     let b = Hex::new(5, 5);
+    let line = a.line_to(b);
+    let len = line.len();
+    let line: Vec<_> = line.collect();
+    assert_eq!(line.len(), len);
     assert_eq!(
-        a.line_to(b).collect::<Vec<_>>(),
+        line,
         vec![
             a,
             Hex::new(0, 1),
@@ -340,8 +348,9 @@ fn line_to() {
 #[test]
 fn empty_line_to() {
     let start = Hex::new(3, -7);
-    let line: Vec<_> = start.line_to(start).collect();
-    assert_eq!(line, vec![start]);
+    let line = start.line_to(start);
+    assert_eq!(line.len(), 1);
+    assert_eq!(line.collect::<Vec<_>>(), vec![start]);
 }
 
 #[test]
@@ -373,6 +382,22 @@ fn range_count() {
     assert_eq!(Hex::range_count(15), 721);
 }
 
+#[test]
+fn range() {
+    let hex = Hex::new(13, -54);
+    let mut range = hex.range(16);
+    assert_eq!(range.len(), Hex::range_count(16));
+    assert_eq!(range.size_hint(), (range.len(), Some(range.len())));
+    println!("{:#?}", range.size_hint());
+    let _ = range.next();
+    println!("{:#?}", range.size_hint());
+    assert_eq!(range.len(), Hex::range_count(16) - 1);
+    assert_eq!(range.size_hint(), (range.len(), Some(range.len())));
+    let _ = range.next();
+    assert_eq!(range.len(), Hex::range_count(16) - 2);
+    assert_eq!(range.size_hint(), (range.len(), Some(range.len())));
+}
+
 #[test]
 fn ring() {
     let hex = Hex::ZERO;
@@ -429,6 +454,40 @@ fn custom_ring() {
     }
 }
 
+#[test]
+fn ring_edge() {
+    let hex = Hex::new(-189, 35);
+    let edge = hex.ring_edge(48, DiagonalDirection::TopRight);
+    assert_eq!(edge.len(), edge.count());
+    // empty
+    let edge = hex.ring_edge(0, DiagonalDirection::TopRight);
+    let len = edge.len();
+    let edge: Vec<_> = edge.collect();
+    assert_eq!(edge.len(), len);
+    assert_eq!(edge, vec![hex]);
+    // len 1
+    let edge = hex.ring_edge(1, DiagonalDirection::TopRight);
+    let len = edge.len();
+    let edge: Vec<_> = edge.collect();
+    assert_eq!(edge.len(), len);
+    assert_eq!(edge.len(), 2);
+}
+
+#[test]
+#[allow(clippy::cast_possible_truncation)]
+fn wedge() {
+    let hex = Hex::new(98, -123);
+    for dir in DiagonalDirection::ALL_DIRECTIONS {
+        for range in 0..=30 {
+            let wedge: Vec<_> = hex.wedge(0..=range, dir).collect();
+            assert_eq!(wedge.len() as u32, range * (range + 3) / 2 + 1);
+            assert_eq!(wedge.len() as u32, Hex::wedge_count(range));
+            let full_wedge = hex.full_wedge(range, dir);
+            assert_eq!(full_wedge.len(), wedge.len());
+        }
+    }
+}
+
 #[test]
 fn spiral_range() {
     let expected: Vec<_> = Hex::ZERO.range(10).collect();