Skip to main content

dbsp/
dynamic.rs

1//! This module contains type and trait declarations that support the DBSP dynamic dispatch
2//! architecture.
3//!
4//! ## Background
5//!
6//! DBSP operators manipulate batches of records that represent keys, values, and weights
7//! in various collections. Each record is represented as a concrete Rust type, e.g., a struct
8//! or a tuple.  Operators are parameterized by the types of their input and output batches,
9//! which are in turn parameterized by the types of keys, values, and weights in the batch.
10//! This means that, had we used static typing (which is the default in Rust), the Rust
11//! compiler would generate a specialized implementation of each operator for each combination
12//! of input and output types that occur in the program.  In an earlier version of this
13//! library, this approach led to very long compilation times for even medium-sized programs.
14//!
15//! The solution we adopt relies on dynamic dispatch instead.  We bundle primitive operations
16//! on types (comparison, cloning, etc.) as object-safe traits and expose them to operators
17//! as trait objects.  This speeds up compilation without sacrificing much performance.
18//!
19//! Implementing this approach involves some machinery.  First, trait objects cannot be
20//! efficiently combined into any kind of containers (vectors, sets, or even tuples or structs)
21//! without wrapping each object in a `Box` or some other heap-allocated type, leading to many
22//! small allocations.  To avoid this, we represent such containers as separate trait objects.
23//! For instance, options (`Option<T>`), 2-tuples (`(T1, T2)`), vectors (`Vec<T>`), and sets
24//! (`Set<T>`) are modeled as traits.
25//!
26//! Second, this design introduces unsafe code that cannot be nicely encapsulated.  By
27//! replacing concrete types with trait objects, we lose the ability to distinguish between trait
28//! objects backed by different types.  Operations like comparison and cloning must downcast
29//! their arguments to the same concrete type.  This can be done safely by checking the
30//! [`TypeId`](core::any::TypeId)s
31//! of the arguments, but this check is expensive in Rust when performed on the critical
32//! path.  We therefore elide this check in release builds, making all such operations unsafe.
33//!
34//! Finally, operators require the ability to create new values, which in turn requires passing
35//! a **factory** object for each type the operator may need to instantiate.  Most operators
36//! (as well as low-level data structures they use internally like batches and traces) require
37//! multiple factory objects.
38//!
39//! ## Dynamic API design considerations
40//!
41//! Since trait objects don't have a statically known size, they cannot be returned by value
42//! (without boxing them, which we don't want to do in most cases).  We therefore pass mutable
43//! references for functions to write return values to.  This requires the caller to allocate
44//! space for the return value using a factory object.
45//!
46//! Likewise, trait objects cannot be passed by value; therefore functions that would normally
47//! take an owned value instead take a mutable reference, which they are allowed to use without
48//! cloning, e.g., with [`ClonableTrait::move_to`](`crate::dynamic::ClonableTrait::move_to`), which
49//! moves the value, leaving the default value in its place.
50//!
51//! ## Trait hierarchy
52//!
53//! We encapsulate common operations in a hierarchy of object-safe traits, i.e., traits for which
54//! the compiler can build vtables and expose them as trait objects (`dyn <Trait>`).
55//!
56//! ### Basic traits
57//!
58//! The following traits must be implemented for all values that DBSP can compute on.
59//! All of these traits include blanket implementation for all the types that can support them:
60//!
61//! * [`AsAny`] - convert a reference to a type to `&dyn Any`.
62//! * [`Clonable`] - an object-safe version of `Clone`.
63//! * [`Comparable`] - an object-safe version of `Ord`.
64//! * [`SerializeDyn`] - an object-safe version of `rkyv::Serialize`.
65//! * [`DeserializableDyn`] - an object-safe version of `rkyv::Deserialize`.
66//! * [`Data`] - the lowest common denominator for all types that DBSP can compute on.
67//!   Combines all of the above traits, along with `Debug`, `SizeOf`, and `Send`.  This
68//!   is an object-safe version of [`crate::DBData`].
69//!
70//! In addition, the [`Erase`] trait is provided to convert concrete types into trait objects.
71//!
72//! ### Trait object traits
73//!
74//! The following traits are meant to be implemented by **trait objects** rather than regular
75//! types, i.e., an instance of one of these traits is a `dyn Trait` (which is a type in Rust).
76//! The reason we need these traits is ergonomics, since not all useful behaviors can be
77//! captured nicely in an object-safe way.  For instance `Eq`, `Ord`, and `Clone` traits
78//! are not object-safe, so we cannot expose them directly via the vtable.  We instead introduce
79//! [`Comparable`] and [`Clonable`] traits mentioned above.  These traits define an unsafe
80//! API that accepts one of the arguments as a raw byte pointer (`*mut u8`).  These are not
81//! meant to be used directly.  Instead, we use them to implement `Eq`, `Ord`, and `Clone` for
82//! trait objects.
83//!
84//! * [`DowncastTrait`] - cast a trait object reference to a concrete type.
85//! * [`ClonableTrait`] - trait for trait objects whose concrete type can be cloned and moved.
86//! * [`ArchiveTrait`] - trait for trait objects that can be serialized and deserialized with `rkyv`.
87//! * [`DataTrait`] - the lowest common denominator for all trait objects that DBSP can compute on.
88//!   Combines all of the above traits, along with [`Data`].  This
89//!   is the trait object version of [`crate::DBData`].
90//!
91//! ### Creating trait objects
92//!
93//! There are three ways to create a trait object:
94//!
95//! * The [`Factory`] trait can be used to allocate an instance of a concrete type with the default
96//!   value on the heap or on the stack and return a reference to it as a trait object.
97//!
98//! * The [`Erase`] trait converts a reference to a concrete type into a trait object.
99//!
100//! * [`DeserializableDyn`] and [`DeserializeDyn`] deserialize values of the concrete type from
101//!   a byte array or from an archived representation respectively and returns them as trait
102//!   objects.
103//!
104//! ### Derived traits
105//!
106//! The traits described above are applicable to all DBSP values.  The following traits specify
107//! extended functionality available for certain types and their combinations.
108//!
109//! * [`DynWeight`] ([`WeightTrait`]) - types with the addition operation and a zero element.
110//!   This is an object-safe version of [`crate::DBWeight`].
111//! * [`DynOpt`] - a dynamically typed version of the `Option` type.
112//! * [`DynPair`] - a dynamically typed version of a two-tuple `(T1, T2)`.
113//! * [`DynVec`] - a dynamically typed vector.
114//! * [`DynSet`] - a dynamically typed B-tree set.
115//! * [`DynPairs`] - a vector of pairs.
116//! * [`DynWeightedPairs`] - a vector of key-value pairs, where the value behaves as weight,
117//!   meaning that tuples with the same key can be consolidated by adding their weights.
118
119mod clonable;
120mod comparable;
121pub(crate) mod data;
122mod declare_trait_object;
123mod downcast;
124mod erase;
125mod factory;
126mod lean_vec;
127mod option;
128pub mod pair;
129mod pairs;
130pub(crate) mod rkyv;
131mod set;
132mod vec;
133mod weight;
134mod weighted_pairs;
135
136pub use clonable::{Clonable, ClonableTrait};
137pub use comparable::Comparable;
138pub use data::{Data, DataTrait, DataTraitTyped, DynBool, DynData, DynDataTyped, DynUnit};
139pub use downcast::{AsAny, DowncastTrait};
140pub use erase::Erase;
141pub use factory::{Factory, WithFactory};
142pub use lean_vec::{LeanVec, RawIter, RawVec};
143pub use option::{DynOpt, Opt};
144pub use pair::{DynPair, Pair};
145pub use pairs::{DynPairs, Pairs, PairsTrait};
146pub use rkyv::{
147    ArchiveTrait, ArchivedDBData, DeserializableDyn, DeserializeDyn, DeserializeImpl, SerializeDyn,
148};
149pub use set::{BSet, DynSet, Set, SetTrait};
150pub use vec::{DynVec, VecTrait, Vector};
151pub use weight::{DynWeight, DynWeightTyped, Weight, WeightTrait, WeightTraitTyped};
152pub use weighted_pairs::{DynWeightedPairs, WeightedPairs, WeightedPairsTrait};