table_row.rs

  1//! A newtype for a table row that enforces a fixed column count at runtime.
  2//!
  3//! This type ensures that all rows in a table have the same width, preventing accidental creation or mutation of rows with inconsistent lengths.
  4//! It is especially useful for CSV or tabular data where rectangular invariants must be maintained, but the number of columns is only known at runtime.
  5//! By using `TableRow`, we gain stronger guarantees and safer APIs compared to a bare `Vec<T>`, without requiring const generics.
  6
  7use std::{
  8    any::type_name,
  9    ops::{
 10        Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
 11    },
 12};
 13
 14#[derive(Clone, Debug, PartialEq, Eq)]
 15pub struct TableRow<T>(Vec<T>);
 16
 17impl<T> TableRow<T> {
 18    pub fn from_element(element: T, length: usize) -> Self
 19    where
 20        T: Clone,
 21    {
 22        Self::from_vec(vec![element; length], length)
 23    }
 24
 25    /// Constructs a `TableRow` from a `Vec<T>`, panicking if the length does not match `expected_length`.
 26    ///
 27    /// Use this when you want to ensure at construction time that the row has the correct number of columns.
 28    /// This enforces the rectangular invariant for table data, preventing accidental creation of malformed rows.
 29    ///
 30    /// # Panics
 31    /// Panics if `data.len() != expected_length`.
 32    pub fn from_vec(data: Vec<T>, expected_length: usize) -> Self {
 33        Self::try_from_vec(data, expected_length).unwrap_or_else(|e| {
 34            let name = type_name::<Vec<T>>();
 35            panic!("Expected {name} to be created successfully: {e}");
 36        })
 37    }
 38
 39    /// Attempts to construct a `TableRow` from a `Vec<T>`, returning an error if the length does not match `expected_len`.
 40    ///
 41    /// This is a fallible alternative to `from_vec`, allowing you to handle inconsistent row lengths gracefully.
 42    /// Returns `Ok(TableRow)` if the length matches, or an `Err` with a descriptive message otherwise.
 43    pub fn try_from_vec(data: Vec<T>, expected_len: usize) -> Result<Self, String> {
 44        if data.len() != expected_len {
 45            Err(format!(
 46                "Row length {} does not match expected {}",
 47                data.len(),
 48                expected_len
 49            ))
 50        } else {
 51            Ok(Self(data))
 52        }
 53    }
 54
 55    /// Returns reference to element by column index.
 56    ///
 57    /// # Panics
 58    /// Panics if `col` is out of bounds (i.e., `col >= self.cols()`).
 59    pub fn expect_get(&self, col: impl Into<usize>) -> &T {
 60        let col = col.into();
 61        self.0.get(col).unwrap_or_else(|| {
 62            panic!(
 63                "Expected table row of `{}` to have {col:?}",
 64                type_name::<T>()
 65            )
 66        })
 67    }
 68
 69    pub fn get(&self, col: impl Into<usize>) -> Option<&T> {
 70        self.0.get(col.into())
 71    }
 72
 73    pub fn as_slice(&self) -> &[T] {
 74        &self.0
 75    }
 76
 77    pub fn into_vec(self) -> Vec<T> {
 78        self.0
 79    }
 80
 81    /// Like [`map`], but borrows the row and clones each element before mapping.
 82    ///
 83    /// This is useful when you want to map over a borrowed row without consuming it,
 84    /// but your mapping function requires ownership of each element.
 85    ///
 86    /// # Difference
 87    /// - `map_cloned` takes `&self`, clones each element, and applies `f(T) -> U`.
 88    /// - [`map`] takes `self` by value and applies `f(T) -> U` directly, consuming the row.
 89    /// - [`map_ref`] takes `&self` and applies `f(&T) -> U` to references of each element.
 90    pub fn map_cloned<F, U>(&self, f: F) -> TableRow<U>
 91    where
 92        F: FnMut(T) -> U,
 93        T: Clone,
 94    {
 95        self.clone().map(f)
 96    }
 97
 98    /// Consumes the row and transforms all elements within it in a length-safe way.
 99    ///
100    /// # Difference
101    /// - `map` takes ownership of the row (`self`) and applies `f(T) -> U` to each element.
102    /// - Use this when you want to transform and consume the row in one step.
103    /// - See also [`map_cloned`] (for mapping over a borrowed row with cloning) and [`map_ref`] (for mapping over references).
104    pub fn map<F, U>(self, f: F) -> TableRow<U>
105    where
106        F: FnMut(T) -> U,
107    {
108        TableRow(self.0.into_iter().map(f).collect())
109    }
110
111    /// Borrows the row and transforms all elements by reference in a length-safe way.
112    ///
113    /// # Difference
114    /// - `map_ref` takes `&self` and applies `f(&T) -> U` to each element by reference.
115    /// - Use this when you want to map over a borrowed row without cloning or consuming it.
116    /// - See also [`map`] (for consuming the row) and [`map_cloned`] (for mapping with cloning).
117    pub fn map_ref<F, U>(&self, f: F) -> TableRow<U>
118    where
119        F: FnMut(&T) -> U,
120    {
121        TableRow(self.0.iter().map(f).collect())
122    }
123
124    /// Number of columns (alias to `len()` with more semantic meaning)
125    pub fn cols(&self) -> usize {
126        self.0.len()
127    }
128}
129
130///// Convenience traits /////
131pub trait IntoTableRow<T> {
132    fn into_table_row(self, expected_length: usize) -> TableRow<T>;
133}
134impl<T> IntoTableRow<T> for Vec<T> {
135    fn into_table_row(self, expected_length: usize) -> TableRow<T> {
136        TableRow::from_vec(self, expected_length)
137    }
138}
139
140// Index implementations for convenient access
141impl<T> Index<usize> for TableRow<T> {
142    type Output = T;
143
144    fn index(&self, index: usize) -> &Self::Output {
145        &self.0[index]
146    }
147}
148
149impl<T> IndexMut<usize> for TableRow<T> {
150    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
151        &mut self.0[index]
152    }
153}
154
155// Range indexing implementations for slice operations
156impl<T> Index<Range<usize>> for TableRow<T> {
157    type Output = [T];
158
159    fn index(&self, index: Range<usize>) -> &Self::Output {
160        <Vec<T> as Index<Range<usize>>>::index(&self.0, index)
161    }
162}
163
164impl<T> Index<RangeFrom<usize>> for TableRow<T> {
165    type Output = [T];
166
167    fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
168        <Vec<T> as Index<RangeFrom<usize>>>::index(&self.0, index)
169    }
170}
171
172impl<T> Index<RangeTo<usize>> for TableRow<T> {
173    type Output = [T];
174
175    fn index(&self, index: RangeTo<usize>) -> &Self::Output {
176        <Vec<T> as Index<RangeTo<usize>>>::index(&self.0, index)
177    }
178}
179
180impl<T> Index<RangeToInclusive<usize>> for TableRow<T> {
181    type Output = [T];
182
183    fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {
184        <Vec<T> as Index<RangeToInclusive<usize>>>::index(&self.0, index)
185    }
186}
187
188impl<T> Index<RangeFull> for TableRow<T> {
189    type Output = [T];
190
191    fn index(&self, index: RangeFull) -> &Self::Output {
192        <Vec<T> as Index<RangeFull>>::index(&self.0, index)
193    }
194}
195
196impl<T> Index<RangeInclusive<usize>> for TableRow<T> {
197    type Output = [T];
198
199    fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {
200        <Vec<T> as Index<RangeInclusive<usize>>>::index(&self.0, index)
201    }
202}
203
204impl<T> IndexMut<RangeInclusive<usize>> for TableRow<T> {
205    fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {
206        <Vec<T> as IndexMut<RangeInclusive<usize>>>::index_mut(&mut self.0, index)
207    }
208}