dataloader_rs/
transform.rs1use crate::error::Result;
4
5pub trait Transform<In>: Send + Sync + 'static {
24 type Output: Send + 'static;
26
27 fn apply(&self, index: usize, item: In) -> Result<Self::Output>;
29
30 fn is_identity(&self) -> bool {
33 false
34 }
35
36 fn then<T>(self, next: T) -> Then<Self, T>
38 where
39 Self: Sized,
40 T: Transform<Self::Output>,
41 {
42 Then {
43 first: self,
44 second: next,
45 }
46 }
47
48 fn apply_batch(&self, indices: &[usize], items: Vec<In>) -> Result<Vec<Self::Output>> {
52 items
53 .into_iter()
54 .zip(indices)
55 .map(|(item, &index)| self.apply(index, item))
56 .collect()
57 }
58}
59
60impl<In, Out, F> Transform<In> for F
61where
62 F: Fn(usize, In) -> Result<Out> + Send + Sync + 'static,
63 Out: Send + 'static,
64{
65 type Output = Out;
66
67 fn apply(&self, index: usize, item: In) -> Result<Out> {
68 self(index, item)
69 }
70}
71
72#[derive(Clone, Copy, Debug, Default)]
74pub struct Identity;
75
76impl<In: Send + 'static> Transform<In> for Identity {
77 type Output = In;
78
79 fn apply_batch(&self, _indices: &[usize], items: Vec<In>) -> Result<Vec<In>> {
80 Ok(items)
81 }
82
83 fn apply(&self, _index: usize, item: In) -> Result<In> {
84 Ok(item)
85 }
86
87 fn is_identity(&self) -> bool {
88 true
89 }
90}
91
92#[derive(Clone, Debug)]
94pub struct Then<A, B> {
95 first: A,
96 second: B,
97}
98
99impl<In, A, B> Transform<In> for Then<A, B>
100where
101 A: Transform<In>,
102 B: Transform<A::Output>,
103{
104 type Output = B::Output;
105
106 fn apply(&self, index: usize, item: In) -> Result<Self::Output> {
107 self.second.apply(index, self.first.apply(index, item)?)
108 }
109}
110
111pub(crate) fn apply_all<In, T: Transform<In>>(
113 transform: &T,
114 indices: &[usize],
115 items: Vec<In>,
116) -> Result<Vec<T::Output>> {
117 if items.len() != indices.len() {
119 return Err(format!(
120 "get_batch returned {} items for {} indices",
121 items.len(),
122 indices.len()
123 )
124 .into());
125 }
126 transform.apply_batch(indices, items)
127}
128
129#[cfg(test)]
130mod tests {
131 use super::{Identity, Transform, apply_all};
132 use crate::error::Result;
133
134 struct AddOne;
135
136 impl Transform<u32> for AddOne {
137 type Output = u32;
138
139 fn apply(&self, _index: usize, item: u32) -> Result<u32> {
140 item.checked_add(1).ok_or_else(|| "overflow".into())
141 }
142 }
143
144 #[test]
145 fn struct_and_closure_chain() {
146 let t =
147 AddOne.then(|index: usize, x: u32| -> Result<String> { Ok(format!("{index}<{x}>")) });
148 assert_eq!(t.apply(3, 1).unwrap(), "3<2>");
149 assert!(!t.is_identity());
150 assert!(Transform::<u32>::is_identity(&Identity));
151 }
152
153 #[test]
154 fn first_error_stops_the_chain() {
155 let t = AddOne.then(|_: usize, _: u32| -> Result<u32> { panic!("not reached") });
156 assert_eq!(t.apply(0, u32::MAX).unwrap_err().to_string(), "overflow");
157 }
158
159 #[test]
160 fn apply_all_pairs_items_with_indices_and_checks_the_count() {
161 let tag = |index: usize, x: u32| -> Result<(usize, u32)> { Ok((index, x)) };
162 assert_eq!(
163 apply_all(&tag, &[5, 2], vec![10, 20]).unwrap(),
164 vec![(5, 10), (2, 20)]
165 );
166 let err = apply_all(&tag, &[5, 2], vec![10]).unwrap_err();
167 assert_eq!(err.to_string(), "get_batch returned 1 items for 2 indices");
168 }
169
170 #[test]
171 fn identity_hands_the_batch_back_as_is() {
172 let items = vec![10u32, 20];
173 let buffer = items.as_ptr();
174 let out = apply_all(&Identity, &[5, 2], items).unwrap();
175 assert_eq!(out, vec![10, 20]);
176 assert_eq!(out.as_ptr(), buffer);
177 assert!(apply_all(&Identity, &[5, 2], vec![10u32]).is_err());
178 }
179}