Skip to main content

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}