gridly/grid/
setter.rs

1use crate::grid::bounds::BoundsError;
2use crate::grid::Grid;
3use crate::location::{Location, LocationLike};
4
5/// Setter trait for grids. Allows setting and replacing elements in the grid.
6/// Implementors should implement the unsafe [setter][GridSetter::set_unchecked]
7/// and [replacer][GridSetter::replace_unchecked] methods. This trait then
8/// provides implementations of safe, bounds-checked setters.
9///
10pub trait GridSetter: Grid {
11    /// Replace the value at the given `location` with `value`, without
12    /// bounds checking `location`. Returns the previous value in the grid.
13    ///
14    /// Implementors of this method are allowed to skip bounds checking
15    /// `location`. The safe interface to [`GridSetter`] bounds checks its
16    /// arguments where necessary before calling this method.
17    ///
18    /// # Safety
19    ///
20    /// Callers must ensure that the location has been bounds-checked before
21    /// calling this method. The safe interface to `Grid` automatically performs
22    /// this checking for you.
23    #[must_use = "discarded return value of replace_unchecked; consider using set_unchecked"]
24    unsafe fn replace_unchecked(&mut self, location: Location, value: Self::Item) -> Self::Item;
25
26    /// Replace the value at the given `location` with `value`, without bounds
27    /// checking `location`.
28    ///
29    /// Implementors of this method are allowed to skip bounds checking
30    /// `location`. The safe interface to [`GridSetter`] bounds checks its
31    /// arguments where necessary before calling this method.
32    ///
33    /// # Safety
34    ///
35    /// Callers must ensure that the location has been bounds-checked before
36    /// calling this method. The safe interface to `Grid` automatically performs
37    /// this checking for you.
38    unsafe fn set_unchecked(&mut self, location: Location, value: Self::Item);
39
40    /// Replace the value at the given `location` with `value`. Returns the
41    /// previous value in the grid, or an error if the location was out of
42    /// bounds.
43    #[inline]
44    fn replace(
45        &mut self,
46        location: impl LocationLike,
47        value: Self::Item,
48    ) -> Result<Self::Item, BoundsError> {
49        self.check_location(location)
50            .map(move |loc| unsafe { self.replace_unchecked(loc, value) })
51    }
52
53    /// Set the value at the given `location` in the grid. Returns an error
54    /// if the location was out of bounds.
55    #[inline]
56    fn set(&mut self, location: impl LocationLike, value: Self::Item) -> Result<(), BoundsError> {
57        self.check_location(location)
58            .map(move |loc| unsafe { self.set_unchecked(loc, value) })
59    }
60}
61
62impl<G: GridSetter> GridSetter for &mut G {
63    #[inline]
64    unsafe fn replace_unchecked(&mut self, location: Location, value: Self::Item) -> Self::Item {
65        G::replace_unchecked(self, location, value)
66    }
67
68    #[inline]
69    unsafe fn set_unchecked(&mut self, location: Location, value: Self::Item) {
70        G::set_unchecked(self, location, value)
71    }
72
73    #[inline]
74    fn replace(
75        &mut self,
76        location: impl LocationLike,
77        value: Self::Item,
78    ) -> Result<Self::Item, BoundsError> {
79        G::replace(self, location, value)
80    }
81
82    #[inline]
83    fn set(&mut self, location: impl LocationLike, value: Self::Item) -> Result<(), BoundsError> {
84        G::set(self, location, value)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use crate::grid::setter::*;
91    use crate::prelude::*;
92    use crate::range::RangeError;
93    use core::mem::replace;
94
95    /// A 2x2 grid in row-major order.
96    #[derive(Debug, Clone, Default)]
97    struct SimpleGrid<T> {
98        cells: [T; 4],
99    }
100
101    impl<T> SimpleGrid<T> {
102        fn index_of(loc: Location) -> usize {
103            match (loc.row.0, loc.column.0) {
104                (0, 0) => 0,
105                (0, 1) => 1,
106                (1, 0) => 2,
107                (1, 1) => 3,
108                _ => unreachable!(),
109            }
110        }
111    }
112
113    impl<T> GridBounds for SimpleGrid<T> {
114        fn dimensions(&self) -> Vector {
115            Vector::new(2, 2)
116        }
117
118        fn root(&self) -> Location {
119            Location::zero()
120        }
121    }
122
123    impl<T> Grid for SimpleGrid<T> {
124        type Item = T;
125
126        unsafe fn get_unchecked(&self, location: Location) -> &T {
127            self.cells.get_unchecked(Self::index_of(location))
128        }
129    }
130
131    impl<T> GridSetter for SimpleGrid<T> {
132        unsafe fn replace_unchecked(&mut self, location: Location, value: T) -> T {
133            replace(
134                self.cells.get_unchecked_mut(Self::index_of(location)),
135                value,
136            )
137        }
138
139        unsafe fn set_unchecked(&mut self, location: Location, value: T) {
140            *self.cells.get_unchecked_mut(Self::index_of(location)) = value;
141        }
142    }
143
144    static TEST_ROWS: [(Row, Option<RowRangeError>); 3] = [
145        (Row(-5), Some(RangeError::TooLow(Row(0)))),
146        (Row(1), None),
147        (Row(5), Some(RangeError::TooHigh(Row(2)))),
148    ];
149
150    static TEST_COLUMNS: [(Column, Option<ColumnRangeError>); 3] = [
151        (Column(-10), Some(RangeError::TooLow(Column(0)))),
152        (Column(0), None),
153        (Column(10), Some(RangeError::TooHigh(Column(2)))),
154    ];
155
156    #[test]
157    fn test_set() {
158        let mut grid: SimpleGrid<Option<&'static str>> = SimpleGrid::default();
159
160        for &(row, row_error) in &TEST_ROWS {
161            for &(column, column_error) in &TEST_COLUMNS {
162                let location = row + column;
163
164                let result = grid.set(location, Some("Hello"));
165
166                match (row_error, column_error) {
167                    (Some(row), Some(column)) => {
168                        assert_eq!(result, Err(BoundsError::Both { row, column }))
169                    }
170                    (Some(row), None) => assert_eq!(result, Err(BoundsError::Row(row))),
171                    (None, Some(column)) => assert_eq!(result, Err(BoundsError::Column(column))),
172                    (None, None) => assert_eq!(result, Ok(())),
173                }
174            }
175        }
176
177        assert_eq!(&grid.cells, &[None, None, Some("Hello"), None]);
178    }
179
180    #[test]
181    fn test_replace() {
182        let mut grid: SimpleGrid<Option<&'static str>> = SimpleGrid::default();
183        grid.set((1, 0), Some("Hello")).unwrap();
184
185        for &(row, row_error) in &TEST_ROWS {
186            for &(column, column_error) in &TEST_COLUMNS {
187                let location = row + column;
188
189                let result = grid.replace(location, Some("World"));
190
191                match (row_error, column_error) {
192                    (Some(row), Some(column)) => {
193                        assert_eq!(result, Err(BoundsError::Both { row, column }))
194                    }
195                    (Some(row), None) => assert_eq!(result, Err(BoundsError::Row(row))),
196                    (None, Some(column)) => assert_eq!(result, Err(BoundsError::Column(column))),
197                    (None, None) => assert_eq!(result, Ok(Some("Hello"))),
198                }
199            }
200        }
201
202        assert_eq!(&grid.cells, &[None, None, Some("World"), None]);
203    }
204}