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}