Skip to main content

bulks/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![allow(internal_features)]
3#![feature(core_intrinsics)]
4#![feature(rustc_attrs)]
5#![feature(extend_one)]
6#![feature(unboxed_closures)]
7#![feature(const_range)]
8#![feature(exact_size_is_empty)]
9#![feature(iter_array_chunks)]
10#![feature(const_try_residual)]
11#![feature(iter_intersperse)]
12#![feature(const_ops)]
13#![feature(iter_map_windows)]
14#![feature(tuple_trait)]
15#![feature(try_trait_v2)]
16#![feature(ptr_metadata)]
17#![feature(const_trait_impl)]
18#![feature(const_cmp)]
19#![feature(const_default)]
20#![feature(const_clone)]
21#![feature(const_destruct)]
22#![feature(fn_traits)]
23#![feature(const_convert)]
24#![feature(async_fn_traits)]
25#![feature(impl_trait_in_assoc_type)]
26#![feature(const_index)]
27#![feature(try_trait_v2_residual)]
28#![feature(const_try)]
29#![feature(const_precise_live_drops)]
30#![feature(control_flow_into_value)]
31#![feature(trait_alias)]
32#![feature(const_option_ops)]
33#![feature(step_trait)]
34#![feature(const_drop_in_place)]
35#![feature(iterator_try_collect)]
36#![feature(doc_notable_trait)]
37#![feature(mem_copy_fn)]
38#![feature(array_into_iter_constructors)]
39#![feature(decl_macro)]
40#![feature(iter_advance_by)]
41#![feature(maybe_uninit_uninit_array_transpose)]
42#![feature(associated_type_defaults)]
43#![feature(const_eval_select)]
44#![feature(const_array)]
45#![cfg_attr(feature = "alloc", feature(allocator_api))]
46#![feature(specialization)]
47#![feature(generic_const_exprs)]
48#![allow(clippy::map_clone)] // Temporary, because Option::cloned is not const and clippy doesn't get that
49
50//! Composable bulk-iteration.
51//!
52//! This crate adds [`Bulk`]s, which are similar to iterators, except they are stricter. They can only be wholly consumed, where every value is operated on in bulk. This,
53//! unlike with classic [`Iterator`]s, makes them fully compatible with arrays!
54//!
55//! # Example
56//!
57//! ```
58//! use bulks::*;
59//!
60//! let a = [1, 2, 3];
61//!
62//! let b: [_; _] = a.bulk()
63//!     .copied()
64//!     .map(|x| (x - 1) as usize)
65//!     .enumerate()
66//!     .inspect(|(i, x)| assert_eq!(i, x))
67//!     .collect();
68//!
69//! assert_eq!(b, [(0, 0), (1, 1), (2, 2)]);
70//! ```
71//!
72//! # Constraints
73//!
74//! Bulks are subject to some extra constraints that don't affect normal iterators.
75//! In order for a bulk to be evaluated as is, the whole bulk must be consumed.
76//! Alternatively, it can be converted into an iterator to evaluate each iteration seperately.
77//! While iterators can be mutably exhausted, bulks cannot, and are therefore guaranteed to be intact.
78//!
79//! Their constrained nature means fewer operations are possible, but the guarantees it gives makes it possible to use them with arrays while still retaining the array's
80//! length. Operations that preserves the length of the data like [`map`](Bulk::map), [`zip`](Bulk::zip), [`enumerate`](Bulk::enumerate), [`rev`](Bulk::rev) and
81//! [`inspect`](Bulk::inspect) are allowed. By enabling the [`generic_const_exprs`](https://github.com/rust-lang/rust/issues/76560)-feature, some other length-modifying operations are also allowed such as [`flat_map`](Bulk::flat_map), [`flatten`](Bulk::flatten), [`intersperse`](Bulk::intersperse), [`array_chunks`](Bulk::array_chunks) and [`map_windows`](Bulk::map_windows)
82//! since these modify the bulk's length in a predetermined way.
83//! Of course, wholly consuming operations like [`fold`](Bulk::fold), [`try_fold`](Bulk::try_fold), [`reduce`](Bulk::reduce), [`try_reduce`](Bulk::reduce),
84//! [`collect`](Bulk::collect) and [`try_collect`](Bulk::try_collect) are fully supported. There's also [`collect_array`](Bulk::collect_array) and
85//! [`try_collect_array`](Bulk::try_collect_array) to avoid turbofish-syntax when doing [`collect`](Bulk::collect) or [`try_collect`](Bulk::try_collect).
86//!
87//! Any `Bulk` that was created from an array can be collected back into an array, given that the operations done on it makes the length predetermined at compile-time.
88//! Bulks can also be used with other structures, allowing generic implementations that work the same on arrays as with other iterables.
89//!
90//! # Bulk
91//!
92//! The trait [`Bulk`] is similar to [`Iterator`], but lacks the [`next`](Iterator::next) method.
93//! Instead, its function is based on the [`for_each`](Bulk::for_each) and [`try_for_each`](Bulk::try_for_each) methods.
94//!
95//! ```
96//! # #![feature(try_trait_v2)]
97//! use core::ops::Try;
98//!
99//! trait Bulk: IntoIterator
100//! {
101//!     fn len(&self) -> usize;
102//!
103//!     fn for_each<F>(self, f: F)
104//!     where
105//!         Self: Sized,
106//!         F: FnMut(Self::Item);
107//!
108//!     fn try_for_each<F, R>(self, f: F) -> R
109//!     where
110//!         Self: Sized,
111//!         F: FnMut(Self::Item) -> R,
112//!         R: Try<Output = ()>;
113//! }
114//! ```
115//!
116//! [`Bulk`]'s full definition includes a number of other methods as well,
117//! but they are default methods, built on top of [`for_each`](Bulk::for_each) and [`try_for_each`](Bulk::try_for_each), and so you get
118//! them for free.
119//!
120//! Bulks are also composable, and it's common to chain them together to do
121//! more complex forms of processing. See the [Adapters](#adapters) section
122//! below for more details.
123//!
124//! # The three forms of bulk-iteration
125//!
126//! There are three common methods which can create bulks from a collection:
127//!
128//! * [`bulk()`](AsBulk::bulk), which iterates over `&T`.
129//! * [`bulk_mut()`](AsBulk::bulk_mut), which iterates over `&mut T`.
130//! * [`into_bulk()`](IntoBulk::into_bulk), which iterates over `T`.
131//!
132//! These are the in-bulk counterparts of `iter()`, `iter_mut()` and [`into_iter()`](IntoIterator::into_iter).
133//! The trait [`IntoBulk`] provides the method [`into_bulk`](IntoBulk::into_bulk).
134//!
135//! [`bulk()`](AsBulk::bulk) is available for any `T` where `&T` implements [`IntoBulk`], and
136//! [`bulk_mut()`](AsBulk::bulk_mut) is available for any `T` where `&mut T` is [`IntoBulk`].
137//! They are just shorthand for doing [`into_bulk`](IntoBulk::into_bulk) on a reference.
138//!
139//! [`IntoBulk`] is automatically implemented for all [`Bulk`]s.
140//! Other types that can be converted into an [`ExactSizeIterator`] through [`IntoIterator`] also automatically implement [`IntoBulk`],
141//! converting them to a [`bulks::iter::Bulk`](crate::iter::Bulk), however this implementation can be specialized.
142//! For example, arrays specialize this implementation, converting to [`bulks::array::IntoBulk`](crate::array::IntoBulk) instead.
143//! Specializing [`IntoBulk`] is useful for collections whose length must be retained at compile-time, like arrays.
144//!
145//! # Implementing Bulk
146//!
147//! Making your own bulk is a bit similar to making an [`Iterator`], but a little bit less convenient.
148//!
149//! Your bulk needs a corresponding iterator that it can be converted to, which must be an [`ExactSizeIterator`].
150//!
151//! ```
152//! # #![feature(try_trait_v2)]
153//! use core::ops::Try;
154//!
155//! use bulks::*;
156//!
157//! /// An iterator which counts from one to `N`
158//! struct CounterIter<const N: usize>
159//! {
160//!     count: usize,
161//! }
162//!
163//! // we want our count to start at one, so let's add a new() method to help.
164//! // This isn't strictly necessary, but is convenient.
165//! impl<const N: usize> CounterIter<N>
166//! {
167//!     pub fn new() -> Self
168//!     {
169//!         CounterIter { count: 0 }
170//!     }
171//! }
172//!
173//! // Then, we implement `Iterator` for our `CounterIter`:
174//! impl<const N: usize> Iterator for CounterIter<N>
175//! {
176//!     // We will be counting with usize
177//!     type Item = usize;
178//!
179//!     // next() is the only required method
180//!     fn next(&mut self) -> Option<Self::Item>
181//!     {
182//!         // Increment our count. This is why we started at zero.
183//!         self.count += 1;
184//!
185//!         // Check to see if we've finished counting or not.
186//!         if self.count <= N
187//!         {
188//!             Some(self.count)
189//!         }
190//!         else
191//!         {
192//!             None
193//!         }
194//!     }
195//!
196//!     // Since we're implementing `ExactSizeIterator`, it's a good idea to override `size_hint`.
197//!     fn size_hint(&self) -> (usize, Option<usize>)
198//!     {
199//!         let len = self.len();
200//!         (len, Some(len))
201//!     }
202//! }
203//!
204//! // We also need our `CounterIter` to be an `ExactSizeIterator`.
205//! impl<const N: usize> ExactSizeIterator for CounterIter<N>
206//! {
207//!     fn len(&self) -> usize
208//!     {
209//!         N.saturating_sub(self.count)
210//!     }
211//! }
212//!
213//! // Now that we have an iterator we can start defining our bulk-iterator.
214//!
215//! /// A bulk which counts from one to five
216//! struct Counter<const N: usize>;
217//!
218//! // Then, we implement `IntoIterator` for our `Counter`:
219//! impl<const N: usize> IntoIterator for Counter<N>
220//! {
221//!     // We will be counting with usize
222//!     type Item = usize;
223//!     // This is iterator needs to be equivalent to our bulk.
224//!     type IntoIter = CounterIter<N>;
225//!
226//!     fn into_iter(self) -> Self::IntoIter
227//!     {
228//!         CounterIter::new()
229//!     }
230//! }
231//!
232//! // Then, we implement `Bulk` for our `Counter`:
233//! impl<const N: usize> Bulk for Counter<N>
234//! {
235//!     type MinLength = [(); N];
236//!     type MaxLength = [(); N];
237//!
238//!     fn len(&self) -> usize
239//!     {
240//!         N
241//!     }
242//!
243//!     fn for_each<F>(self, mut f: F)
244//!     where
245//!         Self: Sized,
246//!         F: FnMut(Self::Item)
247//!     {
248//!         for i in self
249//!         {
250//!             f(i)
251//!         }
252//!     }
253//!
254//!     fn try_for_each<F, R>(self, mut f: F) -> R
255//!     where
256//!         Self: Sized,
257//!         F: FnMut(Self::Item) -> R,
258//!         R: Try<Output = ()>
259//!     {
260//!         for i in self
261//!         {
262//!             f(i)?
263//!         }
264//!         R::from_output(())
265//!     }
266//! }
267//!
268//! // And now we can use it!
269//! let counter = Counter::<5>;
270//! let result: [_; _] = counter.collect();
271//!
272//! assert_eq!(result, [1, 2, 3, 4, 5]);
273//! ```
274//!
275//! # Adapters
276//!
277//! Just like with iterators there are adapters for bulks.
278//! These are functions which take a [`Bulk`] and return another [`Bulk`].
279//!
280//! Common bulk adapters include [`map`](Bulk::map), [`take`](Bulk::take), and [`rev`](Bulk::rev).
281//! For more, see their documentation.
282//!
283//! # Laziness
284//!
285//! Bulks (and bulk [adapters](#adapters)), just like iterators, are *lazy*. This means that
286//! just creating a bulk doesn't _do_ a whole lot. Nothing really happens
287//! until you consume it. This is sometimes a source of confusion when
288//! creating a bulk solely for its side effects. For example, the [`map`](Bulk::map)
289//! method calls a closure on each element it iterates over:
290//!
291//! ```
292//! # #![allow(unused_must_use)]
293//! # #![allow(map_unit_fn)]
294//! use bulks::*;
295//!
296//! let a = [1, 2, 3, 4, 5];
297//! a.bulk().map(|x| println!("{x}"));
298//! ```
299//!
300//! This will not print any values, as we only created a bulk, rather than
301//! using it. The compiler will warn us about this kind of behavior:
302//!
303//! ```text
304//! warning: unused result that must be used: bulks are lazy and
305//! do nothing unless consumed
306//! ```
307//!
308//! The idiomatic way to write a [`map`](Bulk::map) for its side effects is to use a
309//! `for` loop or call the [`for_each`](Bulk::for_each) method:
310//!
311//! ```
312//! use bulks::*;
313//!
314//! let a = [1, 2, 3, 4, 5];
315//!
316//! a.bulk().for_each(|x| println!("{x}"));
317//! // or
318//! for x in &a
319//! {
320//!     println!("{x}");
321//! }
322//! ```
323//!
324//! Another common way to evaluate a bulk is to use the [`collect`](Bulk::collect)
325//! method to produce a new collection.
326//!
327//! ```
328//! use bulks::*;
329//!
330//! let a = [1, 2, 3, 4, 5];
331//!
332//! let b: [_; _] = a.into_bulk().collect();
333//!
334//! assert_eq!(a, b);
335//! ```
336
337/*
338# MISSING FEATURES:
339- collect_into (requires `Extend` to become a const-trait)
340- const enumerate_with
341*/
342
343#[cfg(feature = "alloc")]
344extern crate alloc;
345
346moddef::moddef!(
347    flat(pub) mod {
348        adapters,
349        impl_array,
350        impl_iter,
351        impl_ndarray for cfg(feature = "ndarray"),
352        impl_slice,
353        impl_vec for cfg(feature = "alloc"),
354        impl_option,
355        bulk,
356        collect_nearest,
357        double_ended_bulk,
358        from_bulk,
359        into_bulk,
360        split_bulk,
361        static_bulk
362    },
363    pub mod {
364        range
365    },
366    mod util
367);
368
369#[cfg(false)]
370pub mod asm
371{
372    use crate::{Bulk, IntoBulk};
373
374    #[unsafe(no_mangle)]
375    pub fn asm_bulk(a: [u8; 4]) -> [u8; 4]
376    {
377        a.into_bulk().collect()
378    }
379
380    #[unsafe(no_mangle)]
381    pub fn asm_swap(a: [u8; 4]) -> [u8; 4]
382    {
383        let mut bulk = a.into_bulk();
384
385        bulk.swap_inplace(0, 1);
386
387        bulk.collect()
388    }
389
390    #[unsafe(no_mangle)]
391    #[inline(never)]
392    pub fn asm_bench_swap(mut a: [u8; 4]) -> [u8; 4]
393    {
394        a.swap(0, 1);
395
396        a
397    }
398}
399
400#[cfg(test)]
401mod tests
402{
403    use crate::*;
404
405    #[test]
406    fn it_works()
407    {
408        let a: [i32; _] = [1, 2, 3];
409
410        let f = |x| (x - 1) as usize;
411
412        let b = a.bulk().copied().map(f).enumerate().inspect(|(i, x)| assert_eq!(i, x)).collect_nearest();
413
414        assert_eq!(b, [(0, 0), (1, 1), (2, 2)] as [(usize, usize); _]);
415    }
416
417    #[cfg(feature = "alloc")]
418    #[test]
419    fn nearest()
420    {
421        let a: [i32; _] = [1, 2, 3];
422
423        let f = |x| (x - 1) as usize;
424
425        let b = a.bulk().copied().map(f).enumerate().inspect(|(i, x)| assert_eq!(i, x));
426
427        fn nearest<T>(bulk: T) -> <T as CollectNearest>::Nearest
428        where
429            T: Bulk
430        {
431            bulk.collect_nearest()
432        }
433
434        let b = nearest(b);
435
436        assert_eq!(b, [(0, 0), (1, 1), (2, 2)] as [(usize, usize); _]);
437    }
438
439    #[test]
440    fn test_option()
441    {
442        let a: [i32; _] = [1];
443        let b: [i32; _] = [];
444
445        let a: Option<i32> = a.into_bulk().map(|x| x + 1).collect();
446        let b: Option<i32> = b.into_bulk().map(|x| x + 1).collect();
447
448        assert_eq!(a, Some(2));
449        assert_eq!(b, None);
450
451        fn maybe<B>(bulk: B)
452        where
453            B: IntoBulk<Item = i32>,
454            Option<i32>: CollectionStrategy<B::IntoBulk, Option<i32>>
455        {
456            let (Some(_) | None): Option<i32> = bulk.into_bulk().collect();
457        }
458
459        maybe(Some(1));
460        maybe([1; 0]);
461        maybe([1]);
462    }
463
464    #[test]
465    fn test_swap() -> Result<(), Box<dyn std::error::Error>>
466    {
467        let a = [1, 2, 3, 4];
468
469        let mut b = a;
470
471        b.bulk_mut().try_swap::<u8>(0, 3)?;
472        b.bulk_mut().try_swap::<u8>(1, 2)?;
473
474        let b = b.into_bulk().rev().collect_array();
475
476        assert_eq!(a, b);
477        println!("{a:?}");
478
479        Ok(())
480    }
481
482    #[test]
483    fn gpa()
484    {
485        #[repr(u8)]
486        enum Grade
487        {
488            A = 5,
489            B = 4,
490            C = 3,
491            D = 2,
492            E = 1
493        }
494
495        const GRADES_UNI_MASTER: [(f32, Grade); 4] = [
496            (7.5, Grade::A), // Innovation with Sensor Technology
497            (7.5, Grade::A), // Electronic Measurement Systems
498            (7.5, Grade::A), // Applied Mathematics
499            (7.5, Grade::D)  // Reliability and Robustness in Sensor Systems
500        ];
501        const GRADES_UNI_BACHELOR: [(u8, Grade); 21] = [
502            (5, Grade::C),  // Ingeniørrollen
503            (5, Grade::A),  // Programmering for beregning
504            (5, Grade::B),  // Elektrisitetslære
505            (5, Grade::D),  // Digitalteknikk
506            (10, Grade::A), // Programmering og mikrokontrollere
507            (10, Grade::A), // Matematikk 1
508            (5, Grade::C),  // Fysikk 1 - Mekanikk
509            (5, Grade::A),  // Elektrisitetslære 2
510            (5, Grade::A),  // Programmerbare logiske kretser
511            (10, Grade::A), // Matematikk 2
512            (5, Grade::C),  // Kommunikasjon
513            (10, Grade::B), // Analog elektronikk
514            (10, Grade::B), // Systems design and engineering
515            (5, Grade::C),  // Statistikk
516            (10, Grade::E), // Signalbehandling
517            (10, Grade::C), // Reguleringsteknikk 1
518            (5, Grade::B),  // Fysikk 2 - Elektromagnetisme
519            (10, Grade::C), // Reguleringsteknikk 2
520            (10, Grade::C), // Matematikk 3
521            (10, Grade::C), // Instrumentering og styring
522            (20, Grade::B)  // Bacheloroppgave - Automatisk gir-system for Lone Wolf ATV
523        ];
524        const GRADES_VGS: [u8; 23] = [
525            5, // Engelsk
526            2, // Spansk II
527            4, // Geografi
528            4, // Historie
529            4, // Kroppsøving
530            4, // Matematikk 1T
531            5, // Naturfag
532            4, // Norsk hovedmål
533            4, // Norsk hovedmål, eksamen
534            3, // Norsk sidemål
535            2, // Norsk sidemål, eksamen
536            3, // Norsk
537            3, // Religion og etikk
538            4, // Samfunnsfag
539            4, // Fysikk 1
540            4, // Fysikk 2
541            5, // Fysikk 2, eksamen
542            3, // Kjemi
543            4, // Informasjonsteknologi 1
544            5, // Informasjonsteknologi 2
545            4, // Teknologi og forskningslære 1
546            3, // Matematikk R1
547            4  // Matematikk R2
548        ];
549
550        let pts_uni_master: f32 = GRADES_UNI_MASTER.into_bulk().map(|(ptr, _)| ptr).sum_from(0.0);
551        let pts_uni_bachelor: f32 = GRADES_UNI_BACHELOR.into_bulk().map(|(ptr, _)| ptr as u16).sum_from(0) as f32;
552
553        let gpa_uni_master: f32 = GRADES_UNI_MASTER.into_bulk().map(|(pts, grade)| pts * (grade as u8) as f32).sum_from(0.0) as f32 / pts_uni_master;
554        let gpa_uni_bachelor: f32 = GRADES_UNI_BACHELOR.into_bulk().map(|(pts, grade)| (pts * grade as u8) as u16).sum_from(0) as f32 / pts_uni_bachelor;
555
556        let gpa_uni = (gpa_uni_master * pts_uni_master + gpa_uni_bachelor * pts_uni_bachelor) / (pts_uni_master + pts_uni_bachelor);
557
558        println!("{}", gpa_uni);
559
560        let gpa_vgs: f32 = GRADES_VGS.into_bulk().map(|grade| grade as u16).sum_from(0) as f32 / GRADES_VGS.len() as f32;
561
562        println!("{}", gpa_vgs);
563    }
564}