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}