pictorus_traits/lib.rs
1//! This crate defines the core traits that define what a "block" is in the Pictorus platform.
2//!
3//! # Pictorus Trait Design
4//! Blocks are the fundamental unit of computation used in the Pictorus GUI. We provide a library
5//! of blocks that cover most use cases, however a major goal of this trait system is to allow
6//! users to implement custom functionality by writing their own implementation of these traits.
7//! The Block traits provide a consistent interface that allows the Front End and Code Generator
8//! to work with all blocks (custom and otherwise) the same way.
9//!
10//! ## Trait Design Goals
11//! 1. Work in `std` and `no_std` environments
12//! - Don't require heap allocation
13//! - Make efficient use of memory and compute resources
14//! 2. Be general enough to cover all block functionality we support
15//! 3. Effectively model the "atomic time-step" model of computation that Pictorus targets
16//! 4. Be amenable to use with our UI and code generation
17//! 5. Be as easy to develop against as possible given the above
18//!
19//! ## Blocks
20//! Blocks can be one of four types:
21//! - Generators: Take no inputs and provide output
22//! - Process: Take input and provide output
23//! - Input: Take no input in the system graph and provide output, their output is expected
24//! to be a result of their interaction with external interfaces (e.g. UDP stream, SPI Device, UART stream, etc.)
25//! - Output: Takes input and doesn't provide an output to the model graph. Similar to Input blocks,
26//! these provide no output to the system graph but are expected to have some sort of side-effect or external output as telemetry,
27//! hardware interaction, etc.
28//!
29//! Any block that has an output edge (i.e. all but `Output` blocks) must store a copy of its output as a member of the block itself
30//! (or at the very least somewhere with the same lifetime as the block itself). This is because output from a block is distributed
31//! as an immutable reference to that internal copy which is then passed to any block(s) that use that output as input into themselves.
32//! (This is a slight fib, `Scalar` values are returned by value and therefore a block does not technically need to keep a copy of the
33//! output value locally)
34//!
35//! Every block trait defines a "tick" function (`generate`, `process`, `input`, and `output` respectively for the list above).
36//! In a Pictorus application a given model is executed by running the tick function of every block once per time step.
37//! Blocks should never assume they will be run at a specific time-step since that is globally configurable.
38//! Further, if at all possible, a block should not assume that the application will perfectly maintain consistent
39//! time-steps during execution. Blocks are passed a [`Context`] during each tick that gives the true timing of a tick which
40//! should be used to ensure that delayed or skipped time-steps don't cause anymore disruption to the output than necessary
41//! (e.g. If you are generating a sine wave and time-step is delayed the output should give the value of that sine function
42//! at the new tick-time not what it would have been if we hadn't been delayed, this avoids drift of a long running system).
43//! Although execution of every block in a model takes a non-zero amount of time, during a given time-step every block that
44//! is called will be passed the same time in the context they are given so that all the computation in a given time-step is
45//! "atomic" with respect to all the blocks that use timing information in their execution.
46//!
47//! ### The Block Traits
48//! #### `GeneratorBlock`
49//! Defines an `Output` associated type that must impl `Pass`.
50//!
51//! Defines a "tick" function of `generate` that must be able to generate an output using some combination of the passed in `Context`,
52//! the passed in `Parameters` and the block's internal state
53//!
54//! #### `ProcessBlock`
55//! These make up of the majority of blocks in Pictorus. They must define an `Output` associated type with the same restrictions as
56//! described above. They additionally define an `Input` associated type with the same bounds.
57//!
58//! The tick function `process` is passed in the blocks parameters, a `Context` and an Input and returns an Output.
59//! Blocks may also have internal state that affects the output for a given set of arguments passed into `process`
60//!
61//! #### `InputBlock`
62//! Defines an `Output` associated type that must impl `Pass`.
63//!
64//! It is expected that on each call of the "tick" function the output generated (if any) will be from some sort of external source
65//! (e.g. sensor hardware, a web socket, etc.). For this reason an Input Block will almost always have to be tightly coupled to the
66//! rest of the application that the model is being compiled into
67//!
68//! #### `OutputBlock`
69//! Defines an `Input` associated type that must impl `Pass`.
70//!
71//! As with the Input Block, implementors of this trait are expected to be Outputting data to some device or interface external
72//! to the model, and will also be tightly coupled to the rest of the application.
73//!
74//! ## Edges
75//! Edges in the system graph represent data traveling between blocks. Data is transmitted once per system tick.
76//! The data can take the following forms:
77//! - Scalar: A single value, through generics it can be an `f32`, `f64`, `bool`, or `u8`
78//! - A fixed size 2D matrix: All the elements of a given matrix must be the same type, that type can be any of the scalar types
79//! - A `ByteSliceSignal` which represents a 1D slice of `u8`: Used for communicating byte streams
80//!
81//! ## Parameters
82//! Parameters alter the behavior and sometimes connectivity of blocks. There exist two types of parameters;
83//! Compile Time Parameters and Run Time Parameters, both types of parameters must be set to a value in the
84//! UI during model development.
85//!
86//! Compile Time Parameters are parameters that affect the size, type, or number of input and output edges of a block or
87//! that affect the generated code for interfacing with hardware (e.g. PWM pin selection), they are relatively easy to
88//! identify as they will appear as generics in the block definition. They cannot be changed once a model has been compiled
89//! into an executable.
90//!
91//! Runtime Parameters on the other hand will use the value set during model development by default, but can later be changed
92//! at runtime, or when the model is launched. Each block implementation indicates its Runtime Parameters by setting the `Parameters`
93//! associated type of their trait. Each call to a given block's "tick" function will always be passed an immutable reference to
94//! one of these `Parameters`. This design allows the application that the model has been compiled into to handle the details of
95//! parameter management as an implementation detail beyond the scope of responsibility of Blocks.
96//!
97//! Alternatives were explored to have Blocks store their own parameters with getter and setter functions,
98//! or to have each block be given a mutex guarded pointer to a copy of their parameters at construction.
99//! However, the design described above was chosen because it offered the best ergonomics for block writers while still allowing
100//! parameters to be changed, saved, etc.
101//!
102//! ## Context and Runtime
103//! The `Runtime` is not a formal trait but should be a platform specific way to track and control the flow of time.
104//! The `Runtime` is responsible for keeping track of the elapsed time, the timestep increment, and correctly incrementing time.
105//! Incrementing the timestep is platform and framework specific and may be a software timer, hardware timer, async method,
106//! or another approach to ensure the program time "ticks" in a controlled way.
107//!
108//! A `Runtime` should generate a struct implementing the `Context` trait at the start of a "tick" which is an immutable
109//! representation of elapsed time and timestep increment of the program for the current "tick" iteration.
110//! A `Context` is required to be passed into any `Block` that is runnable, which may or may not use the `Context` internally
111//! to process data.
112//!
113//! ## Edge Details
114//! In the Pictorus application, edges are the connections between blocks. As far as blocks are concerned they must define their
115//! `Inputs` and `Outputs` types as appropriate for their functionality and the Pictorus code generation will handle the work of
116//! actually assigning block outputs to variables and passing them into to blocks as parameters of their "tick" function.
117//!
118//! In the case a block has more than one output it is still modeled as a single `Inputs` type. That single type will be a tuple
119//! of each of the individual input edges the block needs.
120//!
121//! In order to support multiple outputs from a block (e.g. "Is Stale" on quite a few of our existing blocks), the `Outputs`
122//! associated type should be defined as a tuple of the outputs.
123//! Code generation can then use [tuple destructuring](https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring/destructure_tuple.html)
124//! when assigning names to the outputs (e.g. `let (foo_data, foo_is_stale) = foo_block.process(...);` ).
125//!
126//! ### Edge Data Types
127//! The `Pass` trait bounds are the top level trait bounds on the various input and output types of the block traits.
128//! Those traits are both "Sealed" and such block implementors can not implement them over arbitrary types.
129//! The details of those two traits are explained in the following section but this section will explain the set of types
130//! that implement them.
131//!
132//! The following types can be used as edge data:
133//! - `Scalar`s: The `Scalar` trait is implemented for; `f32`, `f64`, `u8`, `u16`, and `bool`.
134//! They represent the individual values in the system
135//! - [`ByteSliceSignal`]: Used for byte streams, this is essentially a stand-in for `[u8]`.
136//! This is necessary because `[u8]` is not a `Sized` type and therefore there are constraints on where it can show up in types definitions.
137//! - `Matrix<const NROWS: usize, const NCOLS: usize, T: Scalar>`: This type is used to model matrices of a singular scalar type
138//! (as well as vectors; being a special case of matrices where one dimension has a size of 1). They have a fixed size that must be known at compile time and don't require on `alloc`. Every element of a given matrix must be the same scalar type
139//!
140//! And finally to support blocks with multiple inputs tuples composed of all types that implement `Pass` will also implement `Pass`.
141//! That is:
142//!
143//! ```rust
144//! impl<A: Pass> Pass for (A){...}
145//! impl<A: Pass, B: Pass> Pass for (A, B){...}
146//! impl<A: Pass, B: Pass, C: Pass> for (A, B, C) {...}
147//! etc...
148//! ```
149//!
150//! ### Pass Trait
151//! The Pass trait allows Scalar types to be passed by value (i.e. by being copied) when being passed between blocks,
152//! while all other types are passed by reference. The trait is defined as:
153//!
154//! ```rust
155//! /// Data can be passed between blocks
156//! pub trait Pass: Sealed + 'static {
157//! /// Whether the data is passed by value or by reference
158//! type By<'a>: Copy;
159//! }
160//! ```
161//!
162//! And for convenience we define a type alias `PassBy`:
163//!
164//! ```rust
165//! pub type PassBy<'a, T> = <T as Pass>::By<'a>;
166//! ```
167//!
168//! Together these allow us to indicate for any type where that type should be passed by value or by reference when used as an edge.
169//! The following blanket impl indicates that all `Scalar` types will be passed by value
170//!
171//! ```rust
172//! impl<T> Pass for T
173//! where
174//! T: Scalar,
175//! {
176//! type By<'a> = Self;
177//!
178//! fn as_by(&self) -> Self::By<'_> {
179//! *self
180//! }
181//! }
182//! ```
183//!
184//! And here is an example implementation for `ByteSliceSignal` slices:
185//!
186//! ```rust
187//! impl Pass for ByteSliceSignal {
188//! type By<'a> = &'a [u8];
189//!
190//! fn as_by(&self) -> Self::By<'_> {
191//! &[]
192//! }
193//! }
194//! ```
195//!
196//! Here you can see that `PassBy<'a, ByteSliceSignal> = &'a [u8]`. Similar implementations are provided for the other collection
197//! edge data types.
198//!
199//! Having `PassBy<'_, T>` available for any type that can be used as edge data allows our block traits to use a definition like
200//! this example from the `ProcessBlock`:
201//!
202//! ```rust
203//! fn process<'b>(
204//! &'b mut self,
205//! context: &dyn Context,
206//! inputs: PassBy<'_, Self::Inputs>,
207//! ) -> PassBy<'b, Self::Output>;
208//! ```
209//!
210//! Here you can see that for a block that accepted `DMatrix<f64>` and returned a `ByteSliceSignal` it would desugar into the following:
211//!
212//! ```rust
213//! fn process<'b>(
214//! &'b mut self,
215//! context: &dyn Context,
216//! inputs: &DMatrix<f64>
217//! ) -> u8;
218//! ```
219//!
220//! and in that way the scalar will be returned by value and the DMatrix will be accepted by reference.
221//!
222//! Finally, this approach scales up naturally when we are using a tuple to accept multiple inputs.
223//! As an example look at this 3 member tuple implementation:
224//! ```rust
225//! impl<A, B, C> Pass for (A, B, C)
226//! where
227//! A: Pass,
228//! B: Pass,
229//! C: Pass,
230//! {
231//! type By<'a> = (PassBy<'a, A>, PassBy<'a, B>, PassBy<'a, C>);
232//! }
233//! ```
234//!
235//! It can be seen than even when some types want to be passed by value and others by reference the
236//! `PassBy<'a, (A,B,C)> = (PassBy<'a, A>, PassBy<'a, B>, PassBy<'a, C> )`
237//!
238//! ### Promotion
239//! The Rust core library is extremely deliberate about conversions between primitive numeric types only offering infallible
240//! conversions where there can be no overflow, or other loss of information (e.g. u8 -> u16 is infallible but i32 -> u64 is
241//! fallible since u64 cannot represent negative values). In order to handle similar cases in our core library we offer the
242//! `Promotion` trait:
243//!
244//! ```rust
245//! pub trait Promote<RHS: Scalar>: Scalar {
246//! type Output: Scalar;
247//!
248//! fn promote_left(self) -> Self::Output;
249//! fn promote_right(rhs: RHS) -> Self::Output;
250//! }
251//! ```
252//!
253//! When a scalar primitive type impls `Promote<T>` for some other scalar type the `Output` associated type indicates the
254//! type that can hold data from both types without loss of information. For convenience we offer the `Promotion<L,R>` type,
255//! defined as `pub type Promotion<L, R> = <L as Promote<R>>::Output;`.
256//! See the below example for how this plays out for the `u8`<->`f32` mapping.
257//!
258//! ```rust
259//! <u8 as Promotion<f32>>::Output = f32
260//! <f32 as Promotion<u8>>::Output = f32
261//!
262//! // This implies:
263//! // Promotion<u8, f32> == Promotion<f32, u8> == f32
264//! ```
265//!
266//! It is worth noting that we implement no-op case of `impl<T:Scalar> Promote<T> for T` for all of our base scalar types.
267//! The end result is that Block writers can then describe their inputs and outputs using the Promotion trait and type.
268//! See this example of the GainBlock which allows the Gain parameter and the passed in data to be of potentially two different
269//! types while still allowing us to specify that the output of that block will be the type that allows us to apply that gain
270//! without loss of information.
271//!
272//! ```rust
273//! impl<const N: usize, G, T> Apply<G> for [T; N]
274//! where
275//! T: Scalar,
276//! G: Promote<T>,
277//! Promotion<G, T>: MulAssign,
278//! {
279//! type Output = [Promotion<G, T>; N];
280//!
281//! fn apply<'s>(
282//! store: &'s mut Option<Self::Output>,
283//! input: PassBy<'_, Self>,
284//! gain: G,
285//! ) -> PassBy<'s, Self::Output> {
286//! let output = store.insert(input.map(<G as Promote<T>>::promote_right));
287//! let gain = Promote::promote_left(gain);
288//! output.iter_mut().for_each(|lhs| lhs.mul_assign(gain));
289//! output
290//! }
291//! }
292//! ```
293//!
294//! While this functionality is undoubtedly useful and we will want to move towards eventually offering it on all of the core
295//! blocks we offer that could benefit from it it doesn't have to be implemented for all of our blocks at first and certainly
296//! won't have to be used in custom blocks where a user knows the type of data they expect to receive or send.
297//! It is just a way to make blocks more general.
298
299#![no_std]
300// and conditionally no_alloc
301
302use core::mem;
303use core::time::Duration;
304
305mod sealed;
306use sealed::Sealed;
307
308pub mod custom_blocks;
309pub use custom_blocks::*;
310
311pub mod tuple_array_interop;
312
313/// A processing block
314pub trait ProcessBlock: Default {
315 // NOTE because of the `Inputs` trait bound; all blocks must have at least *one* input
316 type Inputs: Pass;
317 type Output: Pass;
318 type Parameters;
319
320 fn process<'b>(
321 &'b mut self,
322 parameters: &Self::Parameters,
323 context: &dyn Context,
324 inputs: PassBy<'_, Self::Inputs>,
325 ) -> PassBy<'b, Self::Output>;
326}
327
328pub trait HasIc: ProcessBlock {
329 fn new(parameters: &Self::Parameters) -> Self;
330}
331
332/// A generator block
333///
334/// This block has no inputs
335pub trait GeneratorBlock: Default {
336 type Parameters;
337 type Output: Pass;
338
339 fn generate(
340 &mut self,
341 parameters: &Self::Parameters,
342 context: &dyn Context,
343 ) -> PassBy<Self::Output>;
344}
345
346/// An output block
347///
348/// This block has no output signals and usually performs a "side effect" instead of outputting data.
349pub trait OutputBlock {
350 type Inputs: Pass;
351 type Parameters;
352
353 fn output(
354 &mut self,
355 parameters: &Self::Parameters,
356 context: &dyn Context,
357 inputs: PassBy<'_, Self::Inputs>,
358 );
359}
360
361/// An input block
362///
363/// This block has no inputs signals. Unlike a `GeneratorBlock` it outputs data from the real world rather
364/// than synthetic data.
365pub trait InputBlock {
366 type Output: Pass;
367 type Parameters;
368
369 fn input(
370 &mut self,
371 parameters: &Self::Parameters,
372 context: &dyn Context,
373 ) -> PassBy<'_, Self::Output>;
374}
375
376/// The execution context
377// this trait avoids leaking types associated to the "runtime" into the signature of
378// `{Block,Generator}::run`
379pub trait Context {
380 // This is defined as the actual elapsed time since the last tick, Will return None if the
381 // model is on its first tick
382 fn timestep(&self) -> Option<Duration>;
383 /// Time elapsed since the start of the program / simulation
384 fn time(&self) -> Duration;
385 // Fundamental Timestep, The goal timestep for the model
386 fn fundamental_timestep(&self) -> Duration;
387}
388
389/// Data can be passed between blocks
390pub trait Pass: Sealed + 'static {
391 /// Whether the data is passed by value or by reference
392 type By<'a>: Copy;
393
394 fn as_by(&self) -> Self::By<'_>;
395}
396
397pub type PassBy<'a, T> = <T as Pass>::By<'a>;
398
399impl<T> Pass for T
400where
401 T: Scalar,
402{
403 type By<'a> = Self;
404
405 fn as_by(&self) -> Self::By<'_> {
406 *self
407 }
408}
409
410/// "Scalar" types
411///
412/// Marker trait for small primitives like floats, integers and booleans
413pub trait Scalar: Sealed + Copy + 'static + Default + Into<f64> {}
414
415impl Scalar for bool {}
416impl Sealed for bool {}
417
418impl Scalar for u8 {}
419impl Sealed for u8 {}
420
421impl Scalar for i8 {}
422impl Sealed for i8 {}
423
424impl Scalar for u16 {}
425impl Sealed for u16 {}
426
427impl Scalar for i16 {}
428impl Sealed for i16 {}
429
430impl Scalar for u32 {}
431impl Sealed for u32 {}
432
433impl Scalar for i32 {}
434impl Sealed for i32 {}
435
436impl Scalar for f32 {}
437impl Sealed for f32 {}
438
439impl Scalar for f64 {}
440impl Sealed for f64 {}
441
442/// Auto-promotion
443pub trait Promote<RHS: Scalar>: Scalar {
444 type Output: Scalar
445 + core::ops::Add<Output = Self::Output>
446 + core::ops::Mul<Output = Self::Output>
447 + core::ops::Sub<Output = Self::Output>
448 + core::ops::Div<Output = Self::Output>;
449
450 fn promote_left(self) -> Self::Output;
451 fn promote_right(rhs: RHS) -> Self::Output;
452}
453
454macro_rules! promotions {
455 ($( ( $($from:ident),* ) -> $to:ident ),*) => {
456 $(
457 impl Promote<$to> for $to {
458 type Output = $to;
459
460 fn promote_left(self) -> Self::Output {
461 self
462 }
463
464 fn promote_right(rhs: $to) -> Self::Output {
465 rhs
466 }
467 }
468
469 $(
470 impl Promote<$from> for $to {
471 type Output = $to;
472
473 fn promote_left(self) -> Self::Output {
474 self
475 }
476
477 fn promote_right(rhs: $from) -> Self::Output {
478 rhs as $to
479 }
480 }
481
482 impl Promote<$to> for $from {
483 type Output = $to;
484
485 fn promote_left(self) -> Self::Output {
486 self as $to
487 }
488
489 fn promote_right(rhs: $to) -> Self::Output {
490 rhs
491 }
492 }
493 )*
494 )*
495 };
496}
497
498// TODO add more impls are needed
499promotions! {
500 (u8, u16) -> f32,
501 (f32) -> f64
502}
503
504pub type Promotion<L, R> = <L as Promote<R>>::Output;
505
506// a fixed-size array is like a mathematical vector
507// NOTE the `Scalar` trait bound prevents the creation of nested vectors
508impl<const N: usize, T> Pass for [T; N]
509where
510 T: Scalar,
511{
512 type By<'o> = &'o Self;
513
514 fn as_by(&self) -> Self::By<'_> {
515 self
516 }
517}
518
519impl<const N: usize, T> Sealed for [T; N] where T: Scalar {}
520
521/// This is a Zero-Size-Type that is used as a stand-in for `[u8]` when using the `Pass` trait
522/// Because `[u8]` is a dynamically-sized type it is not possible to use something like `([u8], [u8])` as a generic
523/// parameter. This type is used to work around that limitation. It defines `By = &[u8]` so the correct type is still
524/// passed to or from blocks
525pub struct ByteSliceSignal;
526
527impl Sealed for ByteSliceSignal {}
528
529impl Pass for ByteSliceSignal {
530 type By<'a> = &'a [u8];
531
532 fn as_by(&self) -> Self::By<'_> {
533 &[]
534 }
535}
536
537/// Matrix in column-major order
538// this type is only used as a "DTO" (Data Transfer Object). meaning that this type has
539// no methods and it does NOT enforce any sort of invariants (which is why its field is public).
540// it's only used to *transfer* data between blocks
541//
542// utility functions to convert between this and third-party crates like `nalgebra` are to be
543// provided in a different crate. this also keeps this "interface" crate free from
544// third-party dependencies
545// XXX should this encode row-order vs column-order?
546// NOTE the `Scalar` trait bound prevents the creation of nested matrices
547#[derive(Clone, Copy, Debug, PartialEq)]
548pub struct Matrix<const NROWS: usize, const NCOLS: usize, T>
549where
550 T: Scalar,
551{
552 pub data: [[T; NROWS]; NCOLS],
553}
554
555impl<const NROWS: usize, const NCOLS: usize, T> Default for Matrix<NROWS, NCOLS, T>
556where
557 T: Scalar,
558{
559 fn default() -> Self {
560 Self::zeroed()
561 }
562}
563
564impl<const NROWS: usize, const NCOLS: usize, T> Matrix<NROWS, NCOLS, T>
565where
566 T: Scalar,
567{
568 pub fn zeroed() -> Self {
569 // SAFETY: `T: Scalar` is "sealed" so we know all the types it could be instantiated to and
570 // we also know they are all primitives / "plain old data" so all bits set to zero is
571 // a valid representation
572 Self {
573 data: unsafe { mem::zeroed() },
574 }
575 }
576}
577
578impl<const NROWS: usize, const NCOLS: usize, T> Pass for Matrix<NROWS, NCOLS, T>
579where
580 T: Scalar,
581{
582 type By<'a> = &'a Self;
583
584 fn as_by(&self) -> Self::By<'_> {
585 self
586 }
587}
588
589impl<const NROWS: usize, const NCOLS: usize, T> Sealed for Matrix<NROWS, NCOLS, T> where T: Scalar {}
590
591impl Pass for () {
592 type By<'a> = Self;
593
594 fn as_by(&self) -> Self::By<'_> {
595 *self
596 }
597}
598
599impl Sealed for () {}
600
601impl<A, B> Pass for (A, B)
602where
603 A: Pass,
604 B: Pass,
605{
606 type By<'a> = (PassBy<'a, A>, PassBy<'a, B>);
607
608 fn as_by(&self) -> Self::By<'_> {
609 (self.0.as_by(), self.1.as_by())
610 }
611}
612
613impl<A, B> Sealed for (A, B)
614where
615 A: Pass,
616 B: Pass,
617{
618}
619
620impl<A, B, C> Pass for (A, B, C)
621where
622 A: Pass,
623 B: Pass,
624 C: Pass,
625{
626 type By<'a> = (PassBy<'a, A>, PassBy<'a, B>, PassBy<'a, C>);
627
628 fn as_by(&self) -> Self::By<'_> {
629 (self.0.as_by(), self.1.as_by(), self.2.as_by())
630 }
631}
632impl<A, B, C> Sealed for (A, B, C)
633where
634 A: Pass,
635 B: Pass,
636 C: Pass,
637{
638}
639
640impl<A, B, C, D> Pass for (A, B, C, D)
641where
642 A: Pass,
643 B: Pass,
644 C: Pass,
645 D: Pass,
646{
647 type By<'a> = (PassBy<'a, A>, PassBy<'a, B>, PassBy<'a, C>, PassBy<'a, D>);
648
649 fn as_by(&self) -> Self::By<'_> {
650 (
651 self.0.as_by(),
652 self.1.as_by(),
653 self.2.as_by(),
654 self.3.as_by(),
655 )
656 }
657}
658impl<A, B, C, D> Sealed for (A, B, C, D)
659where
660 A: Pass,
661 B: Pass,
662 C: Pass,
663 D: Pass,
664{
665}
666
667impl<A, B, C, D, E> Pass for (A, B, C, D, E)
668where
669 A: Pass,
670 B: Pass,
671 C: Pass,
672 D: Pass,
673 E: Pass,
674{
675 type By<'a> = (
676 PassBy<'a, A>,
677 PassBy<'a, B>,
678 PassBy<'a, C>,
679 PassBy<'a, D>,
680 PassBy<'a, E>,
681 );
682
683 fn as_by(&self) -> Self::By<'_> {
684 (
685 self.0.as_by(),
686 self.1.as_by(),
687 self.2.as_by(),
688 self.3.as_by(),
689 self.4.as_by(),
690 )
691 }
692}
693impl<A, B, C, D, E> Sealed for (A, B, C, D, E)
694where
695 A: Pass,
696 B: Pass,
697 C: Pass,
698 D: Pass,
699 E: Pass,
700{
701}
702
703impl<A, B, C, D, E, F> Pass for (A, B, C, D, E, F)
704where
705 A: Pass,
706 B: Pass,
707 C: Pass,
708 D: Pass,
709 E: Pass,
710 F: Pass,
711{
712 type By<'a> = (
713 PassBy<'a, A>,
714 PassBy<'a, B>,
715 PassBy<'a, C>,
716 PassBy<'a, D>,
717 PassBy<'a, E>,
718 PassBy<'a, F>,
719 );
720
721 fn as_by(&self) -> Self::By<'_> {
722 (
723 self.0.as_by(),
724 self.1.as_by(),
725 self.2.as_by(),
726 self.3.as_by(),
727 self.4.as_by(),
728 self.5.as_by(),
729 )
730 }
731}
732impl<A, B, C, D, E, F> Sealed for (A, B, C, D, E, F)
733where
734 A: Pass,
735 B: Pass,
736 C: Pass,
737 D: Pass,
738 E: Pass,
739 F: Pass,
740{
741}
742
743impl<A, B, C, D, E, F, G> Pass for (A, B, C, D, E, F, G)
744where
745 A: Pass,
746 B: Pass,
747 C: Pass,
748 D: Pass,
749 E: Pass,
750 F: Pass,
751 G: Pass,
752{
753 type By<'a> = (
754 PassBy<'a, A>,
755 PassBy<'a, B>,
756 PassBy<'a, C>,
757 PassBy<'a, D>,
758 PassBy<'a, E>,
759 PassBy<'a, F>,
760 PassBy<'a, G>,
761 );
762
763 fn as_by(&self) -> Self::By<'_> {
764 (
765 self.0.as_by(),
766 self.1.as_by(),
767 self.2.as_by(),
768 self.3.as_by(),
769 self.4.as_by(),
770 self.5.as_by(),
771 self.6.as_by(),
772 )
773 }
774}
775impl<A, B, C, D, E, F, G> Sealed for (A, B, C, D, E, F, G)
776where
777 A: Pass,
778 B: Pass,
779 C: Pass,
780 D: Pass,
781 E: Pass,
782 F: Pass,
783 G: Pass,
784{
785}
786
787impl<A, B, C, D, E, F, G, H> Pass for (A, B, C, D, E, F, G, H)
788where
789 A: Pass,
790 B: Pass,
791 C: Pass,
792 D: Pass,
793 E: Pass,
794 F: Pass,
795 G: Pass,
796 H: Pass,
797{
798 type By<'a> = (
799 PassBy<'a, A>,
800 PassBy<'a, B>,
801 PassBy<'a, C>,
802 PassBy<'a, D>,
803 PassBy<'a, E>,
804 PassBy<'a, F>,
805 PassBy<'a, G>,
806 PassBy<'a, H>,
807 );
808
809 fn as_by(&self) -> Self::By<'_> {
810 (
811 self.0.as_by(),
812 self.1.as_by(),
813 self.2.as_by(),
814 self.3.as_by(),
815 self.4.as_by(),
816 self.5.as_by(),
817 self.6.as_by(),
818 self.7.as_by(),
819 )
820 }
821}
822impl<A, B, C, D, E, F, G, H> Sealed for (A, B, C, D, E, F, G, H)
823where
824 A: Pass,
825 B: Pass,
826 C: Pass,
827 D: Pass,
828 E: Pass,
829 F: Pass,
830 G: Pass,
831 H: Pass,
832{
833}