208 lines · 6.9 KB · Rust
RawDownload
//! 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)    }}