//! A newtype for a table row that enforces a fixed column count at runtime. //! //! This type ensures that all rows in a table have the same width, preventing accidental creation or mutation of rows with inconsistent lengths. //! 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. //! By using `TableRow`, we gain stronger guarantees and safer APIs compared to a bare `Vec`, without requiring const generics. use std::{ any::type_name, ops::{ Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, }, }; #[derive(Clone, Debug, PartialEq, Eq)] pub struct TableRow(Vec); impl TableRow { pub fn from_element(element: T, length: usize) -> Self where T: Clone, { Self::from_vec(vec![element; length], length) } /// Constructs a `TableRow` from a `Vec`, panicking if the length does not match `expected_length`. /// /// Use this when you want to ensure at construction time that the row has the correct number of columns. /// This enforces the rectangular invariant for table data, preventing accidental creation of malformed rows. /// /// # Panics /// Panics if `data.len() != expected_length`. pub fn from_vec(data: Vec, expected_length: usize) -> Self { Self::try_from_vec(data, expected_length).unwrap_or_else(|e| { let name = type_name::>(); panic!("Expected {name} to be created successfully: {e}"); }) } /// Attempts to construct a `TableRow` from a `Vec`, returning an error if the length does not match `expected_len`. /// /// This is a fallible alternative to `from_vec`, allowing you to handle inconsistent row lengths gracefully. /// Returns `Ok(TableRow)` if the length matches, or an `Err` with a descriptive message otherwise. pub fn try_from_vec(data: Vec, expected_len: usize) -> Result { if data.len() != expected_len { Err(format!( "Row length {} does not match expected {}", data.len(), expected_len )) } else { Ok(Self(data)) } } /// Returns reference to element by column index. /// /// # Panics /// Panics if `col` is out of bounds (i.e., `col >= self.cols()`). pub fn expect_get(&self, col: impl Into) -> &T { let col = col.into(); self.0.get(col).unwrap_or_else(|| { panic!( "Expected table row of `{}` to have {col:?}", type_name::() ) }) } pub fn get(&self, col: impl Into) -> Option<&T> { self.0.get(col.into()) } pub fn as_slice(&self) -> &[T] { &self.0 } pub fn into_vec(self) -> Vec { self.0 } /// Like [`map`], but borrows the row and clones each element before mapping. /// /// This is useful when you want to map over a borrowed row without consuming it, /// but your mapping function requires ownership of each element. /// /// # Difference /// - `map_cloned` takes `&self`, clones each element, and applies `f(T) -> U`. /// - [`map`] takes `self` by value and applies `f(T) -> U` directly, consuming the row. /// - [`map_ref`] takes `&self` and applies `f(&T) -> U` to references of each element. pub fn map_cloned(&self, f: F) -> TableRow where F: FnMut(T) -> U, T: Clone, { self.clone().map(f) } /// Consumes the row and transforms all elements within it in a length-safe way. /// /// # Difference /// - `map` takes ownership of the row (`self`) and applies `f(T) -> U` to each element. /// - Use this when you want to transform and consume the row in one step. /// - See also [`map_cloned`] (for mapping over a borrowed row with cloning) and [`map_ref`] (for mapping over references). pub fn map(self, f: F) -> TableRow where F: FnMut(T) -> U, { TableRow(self.0.into_iter().map(f).collect()) } /// Borrows the row and transforms all elements by reference in a length-safe way. /// /// # Difference /// - `map_ref` takes `&self` and applies `f(&T) -> U` to each element by reference. /// - Use this when you want to map over a borrowed row without cloning or consuming it. /// - See also [`map`] (for consuming the row) and [`map_cloned`] (for mapping with cloning). pub fn map_ref(&self, f: F) -> TableRow where F: FnMut(&T) -> U, { TableRow(self.0.iter().map(f).collect()) } /// Number of columns (alias to `len()` with more semantic meaning) pub fn cols(&self) -> usize { self.0.len() } } ///// Convenience traits ///// pub trait IntoTableRow { fn into_table_row(self, expected_length: usize) -> TableRow; } impl IntoTableRow for Vec { fn into_table_row(self, expected_length: usize) -> TableRow { TableRow::from_vec(self, expected_length) } } // Index implementations for convenient access impl Index for TableRow { type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } } impl IndexMut for TableRow { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] } } // Range indexing implementations for slice operations impl Index> for TableRow { type Output = [T]; fn index(&self, index: Range) -> &Self::Output { as Index>>::index(&self.0, index) } } impl Index> for TableRow { type Output = [T]; fn index(&self, index: RangeFrom) -> &Self::Output { as Index>>::index(&self.0, index) } } impl Index> for TableRow { type Output = [T]; fn index(&self, index: RangeTo) -> &Self::Output { as Index>>::index(&self.0, index) } } impl Index> for TableRow { type Output = [T]; fn index(&self, index: RangeToInclusive) -> &Self::Output { as Index>>::index(&self.0, index) } } impl Index for TableRow { type Output = [T]; fn index(&self, index: RangeFull) -> &Self::Output { as Index>::index(&self.0, index) } } impl Index> for TableRow { type Output = [T]; fn index(&self, index: RangeInclusive) -> &Self::Output { as Index>>::index(&self.0, index) } } impl IndexMut> for TableRow { fn index_mut(&mut self, index: RangeInclusive) -> &mut Self::Output { as IndexMut>>::index_mut(&mut self.0, index) } }