//! 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<T>`, 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<T>(Vec<T>); impl<T> TableRow<T> { 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<T>`, 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<T>, expected_length: usize) -> Self { Self::try_from_vec(data, expected_length).unwrap_or_else(|e| { let name = type_name::<Vec<T>>(); panic!("Expected {name} to be created successfully: {e}"); }) } /// Attempts to construct a `TableRow` from a `Vec<T>`, 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<T>, expected_len: usize) -> Result<Self, String> { 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<usize>) -> &T { let col = col.into(); self.0.get(col).unwrap_or_else(|| { panic!( "Expected table row of `{}` to have {col:?}", type_name::<T>() ) }) } pub fn get(&self, col: impl Into<usize>) -> Option<&T> { self.0.get(col.into()) } pub fn as_slice(&self) -> &[T] { &self.0 } pub fn into_vec(self) -> Vec<T> { 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<F, U>(&self, f: F) -> TableRow<U> 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<F, U>(self, f: F) -> TableRow<U> 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<F, U>(&self, f: F) -> TableRow<U> 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<T> { fn into_table_row(self, expected_length: usize) -> TableRow<T>;}impl<T> IntoTableRow<T> for Vec<T> { fn into_table_row(self, expected_length: usize) -> TableRow<T> { TableRow::from_vec(self, expected_length) }} // Index implementations for convenient accessimpl<T> Index<usize> for TableRow<T> { type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.0[index] }} impl<T> IndexMut<usize> for TableRow<T> { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] }} // Range indexing implementations for slice operationsimpl<T> Index<Range<usize>> for TableRow<T> { type Output = [T]; fn index(&self, index: Range<usize>) -> &Self::Output { <Vec<T> as Index<Range<usize>>>::index(&self.0, index) }} impl<T> Index<RangeFrom<usize>> for TableRow<T> { type Output = [T]; fn index(&self, index: RangeFrom<usize>) -> &Self::Output { <Vec<T> as Index<RangeFrom<usize>>>::index(&self.0, index) }} impl<T> Index<RangeTo<usize>> for TableRow<T> { type Output = [T]; fn index(&self, index: RangeTo<usize>) -> &Self::Output { <Vec<T> as Index<RangeTo<usize>>>::index(&self.0, index) }} impl<T> Index<RangeToInclusive<usize>> for TableRow<T> { type Output = [T]; fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output { <Vec<T> as Index<RangeToInclusive<usize>>>::index(&self.0, index) }} impl<T> Index<RangeFull> for TableRow<T> { type Output = [T]; fn index(&self, index: RangeFull) -> &Self::Output { <Vec<T> as Index<RangeFull>>::index(&self.0, index) }} impl<T> Index<RangeInclusive<usize>> for TableRow<T> { type Output = [T]; fn index(&self, index: RangeInclusive<usize>) -> &Self::Output { <Vec<T> as Index<RangeInclusive<usize>>>::index(&self.0, index) }} impl<T> IndexMut<RangeInclusive<usize>> for TableRow<T> { fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output { <Vec<T> as IndexMut<RangeInclusive<usize>>>::index_mut(&mut self.0, index) }}
//! 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<T>`, 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<T>(Vec<T>);
impl<T> TableRow<T> {
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<T>`, 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<T>, expected_length: usize) -> Self {
Self::try_from_vec(data, expected_length).unwrap_or_else(|e| {
let name = type_name::<Vec<T>>();
panic!("Expected {name} to be created successfully: {e}");
})
}
/// Attempts to construct a `TableRow` from a `Vec<T>`, 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<T>, expected_len: usize) -> Result<Self, String> {
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<usize>) -> &T {
let col = col.into();
self.0.get(col).unwrap_or_else(|| {
panic!(
"Expected table row of `{}` to have {col:?}",
type_name::<T>()
)
})
}
pub fn get(&self, col: impl Into<usize>) -> Option<&T> {
self.0.get(col.into())
}
pub fn as_slice(&self) -> &[T] {
&self.0
}
pub fn into_vec(self) -> Vec<T> {
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<F, U>(&self, f: F) -> TableRow<U>
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<F, U>(self, f: F) -> TableRow<U>
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<F, U>(&self, f: F) -> TableRow<U>
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<T> {
fn into_table_row(self, expected_length: usize) -> TableRow<T>;
}
impl<T> IntoTableRow<T> for Vec<T> {
fn into_table_row(self, expected_length: usize) -> TableRow<T> {
TableRow::from_vec(self, expected_length)
}
}
// Index implementations for convenient access
impl<T> Index<usize> for TableRow<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl<T> IndexMut<usize> for TableRow<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}
// Range indexing implementations for slice operations
impl<T> Index<Range<usize>> for TableRow<T> {
type Output = [T];
fn index(&self, index: Range<usize>) -> &Self::Output {
<Vec<T> as Index<Range<usize>>>::index(&self.0, index)
}
}
impl<T> Index<RangeFrom<usize>> for TableRow<T> {
type Output = [T];
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
<Vec<T> as Index<RangeFrom<usize>>>::index(&self.0, index)
}
}
impl<T> Index<RangeTo<usize>> for TableRow<T> {
type Output = [T];
fn index(&self, index: RangeTo<usize>) -> &Self::Output {
<Vec<T> as Index<RangeTo<usize>>>::index(&self.0, index)
}
}
impl<T> Index<RangeToInclusive<usize>> for TableRow<T> {
type Output = [T];
fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {
<Vec<T> as Index<RangeToInclusive<usize>>>::index(&self.0, index)
}
}
impl<T> Index<RangeFull> for TableRow<T> {
type Output = [T];
fn index(&self, index: RangeFull) -> &Self::Output {
<Vec<T> as Index<RangeFull>>::index(&self.0, index)
}
}
impl<T> Index<RangeInclusive<usize>> for TableRow<T> {
type Output = [T];
fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {
<Vec<T> as Index<RangeInclusive<usize>>>::index(&self.0, index)
}
}
impl<T> IndexMut<RangeInclusive<usize>> for TableRow<T> {
fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {
<Vec<T> as IndexMut<RangeInclusive<usize>>>::index_mut(&mut self.0, index)
}
}