transient/
lib.rs

1//! This crate provides the [`transient::Any`] trait which re-implements the
2//! dynamic typing mechanism provided by [`std::any::Any`] to add support for
3//! types with non-`'static` lifetimes.
4//!
5//! # Introduction
6//! The standard library's [`Any`] trait is used to emulate dynamic typing within
7//! Rust, and is extremely useful in cases where implementing a statically typed
8//! solution would be inconvenient, if not impossible. Examples include storing
9//! heterogeneous values in a `Vec`, or eliminating generic parameters from a
10//! type so that it can be used in object-safe trait methods.
11//!
12//! However, a significant limitation of the `std::any::Any` trait is its `'static`
13//! lifetime bound, which prevents it from being used for types containing any
14//! non-`'static` references. This restriction eliminates many potential use-cases,
15//! and in others it can force users to sacrifice performance by cloning data that
16//! could otherwise be borrowed.
17//!
18//! The crate provides a re-implemented [`Any`] trait that circumvents this limitation
19//! to allow type-erasure to be applied to *transient* (i.e. non-`'static`) types.
20//! This is achieved by modeling a Rust type as decomposable into separate components
21//! for its _raw static data_ and its _lifetime parameters_, as embodied by the
22//! `Static` and `Transience` associated types of the provided [`Transient`] trait.
23//! In this implementation, the `Static` component is used to obtain the unique
24//! [`TypeId`] of the type (which the compiler only hands out for `T: 'static`),
25//! and the `Transience` is used as a generic parameter on the re-implemented
26//! [`Any`] trait to bound the allowable transitions and uphold Rust's strict
27//! safety guarantees.
28//!
29//! # Features
30//! - Near drop-in replacement for `std::any::Any` when dealing with `'static` types
31//! - Familiar extension beyond `std::any::Any` when dealing with non-`'static` types
32//! - Zero run-time cost above that of a standard `dyn Any` cast, with all added
33//!   functionality implemented using the type system
34//! - Safely accounts for the nuances of _subtyping and variance_
35//! - Supports types with any number of generic lifetime parameters with arbitrary
36//!   variance combinations
37//! - Supports types with any number of generic type parameters
38//! - Provides the [`macro@Transient`] `derive` macro to implement the `Transient`
39//!   trait for most types
40//!
41//! # Limitations
42//! - Requires a single `unsafe` trait to be implemented for types wishing to
43//!   utilize the crate's functionality; however, this trait is usually trivial
44//!   to safely implement, and a `derive` macro is provided for common cases
45//! - Only `Sized` types are supported. Removing this restriction would be
46//!   trivial, but makes it awkward to name generic types that require their
47//!   parameters to be `T: Sized` since `T::Static: Sized` must be explicitly
48//!   stated even when `T: Sized` can be implied
49//!
50//! # Examples
51//!
52//! The first step in using this crate is to implement the [`Transient`] trait
53//! for a type. Implementations of this trait are provided for many stdlib
54//! types, it can be derived for most custom types, and it is easy to implement
55//! by hand when more flexibility is needed. Implementations for common types
56//! provided by some 3rd party libraries are also available behind eponymous
57//! feature flags (currently only `ndarray`, `pyo3`, and `numpy`, but feel free
58//! to submit an issue/PR requesting others).
59//!
60//! In the trivial case of a `'static` type with no lifetime parameters, the
61//! `transient` crate's [`Any`] trait can be used just like that of the standard
62//! library once the `Transient` trait has been implemented or derived:
63//! ```
64//! # fn main() {
65//! use transient::*;
66//!
67//! #[derive(Transient, Debug, PartialEq)]
68//! struct Usize(usize);
69//!
70//! let orig = Usize(5);
71//!
72//! let erased: &dyn Any = &orig;
73//! assert_eq!(TypeId::of::<Usize>(), erased.type_id());
74//!
75//! let restored: &Usize = erased.downcast_ref::<Usize>().unwrap();
76//! assert_eq!(restored, &orig);
77//! # }
78//! ```
79//! The trick is that the `Any` trait as used above is actually generic over a
80//! type known as the `Transience`, which defaults to `()`; so the relevant line
81//! in the above snippet actually desugars to `erased: &'_ dyn Any<()> = &orig`.
82//! This form of the `Any` trait only supports `'static` types, just like the
83//! stdlib implementation.
84//!
85//! Where it gets interesting is when a type is *not* `'static`, for which the
86//! `Any` trait can be parameterized by a [`Transience`] type. In the case of
87//! a type with a single lifetime parameter, this can simply be one of three types
88//! provided by this crate, [`Inv`], [`Co`], and [`Contra`], which represent the
89//! three flavors of [variance] a type can have with respect to a lifetime parameter.
90//! While choosing the correct variance would typically be a safety-critical
91//! decision, the valid choices for the variance of a type are bounded by its
92//! implementation of the `Transient` trait, and the compiler will prevent you
93//! from using a transience that would not be sound.
94//!
95//! We will return to the topic of `Transience` in a bit, but for now lets choose
96//! `Inv` (*invariant*) which is the most conservative form of variance that all
97//! (single-lifetime) types can use. To do this, simply replace `dyn Any` with
98//! `dyn Any<Inv>` when coercing a `Box` or reference to the trait object:
99//! ```
100//! # fn main() {
101//! use transient::*;
102//!
103//! #[derive(Transient, Debug, PartialEq)]
104//! struct UsizeRef<'a>(&'a usize);
105//!
106//! let five = 5;
107//! let orig = UsizeRef(&five);
108//!
109//! let erased: &dyn Any<Inv> = &orig;
110//! assert!(erased.is::<UsizeRef>());
111//! assert_eq!(TypeId::of::<UsizeRef>(), erased.type_id());
112//!
113//! let restored: &UsizeRef = erased.downcast_ref().unwrap();
114//! assert_eq!(restored, &orig);
115//! # }
116//! ```
117//!
118//! And that's all it takes! Things get a slightly spicier in more complicated
119//! scenarios, but this crate aims to make the process as painless and intuitive
120//! as possible while safely providing a high degree of flexibility for all the
121//! niche cases you can imagine.
122//!
123//!
124//! # Overview
125//!
126//! ## The `Any` trait
127//! The most important item provided by this crate is the [`Any`] trait, which is
128//! modeled after the standard library's [`std::any::Any`] trait. Much like the
129//! stdlib version, this trait typically appears as the opaque `dyn Any` trait
130//! object that can be _downcast_ back into an original concrete type. The key
131//! difference is that, while the `std::any::Any` trait is implemented for all
132//! `T: 'static`, the [`transient::Any`] trait is instead implemented for all
133//! `T: Transient` (as discussed in the next section). The `transient::Any`
134//! trait is also different in that it has a generic type parameter know as
135//! he `Transience` (discussed in another upcoming section) which is used to
136//! enable the support for non-`'static` types that forms the motivation for
137//! this crate.
138//!
139//! ## The `Transient` Trait
140//! The [`Transient`] trait is an extremely simple, but `unsafe` trait consisting
141//! only of two associated types:
142//! ```skip
143//! pub unsafe trait Transient {
144//!     type Static: 'static;
145//!     type Transience: Transience;
146//!     /* provided methods hidden */
147//! }
148//! ```
149//! The first associated type `Static` is referred to as the *static type* of the
150//! implementing type, and is simply the same type but with its lifetime parameters
151//! replaced by `'static` (e.g., a struct `S<'a, 'b>` would define `Static` as
152//! `S<'static, 'static>`). The static type is used to obtain a [`TypeId`] that
153//! uniquely identifies the (`'static` version of the) erased type so that it can
154//! be safely downcast from an opaque trait object to the concrete type. However,
155//! the compiler only assigns `TypeId`s for `'static` types, so any information
156//! about the true lifetime parameters of the `Transient` type is lost. Another
157//! mechanism is therefore needed to restore this lifetime information so that
158//! the borrow checker can continue to maintain Rust's safety guarantees.
159//!
160//! The second associated type `Transience` provides this mechanism by capturing
161//! the lifetime (and *[variance]*) information that the static type is missing.
162//! To accomplish this, the `transient` crate provides the `Co`, `Contra` and `Inv`
163//! structs that exhibit the 3 forms of variance for a single lifetime parameter,
164//! which can be then combined in tuples to accommodate types with multiple (or
165//! zero) lifetime parameters. This type plays several key roles in the safety
166//! and flexibility of this crate's functionality, as will be discussed below.
167//!
168//! Implementing this trait for a type, either manually or by using the included
169//! [derive macro][macro@Transient], is the key ingredient to utilizing the
170//! functionality of this crate and is discussed in-depth in
171//! [its documentation][Transient].
172//!
173//! ## The `Transience` trait
174//! In common language, **transience** is a noun that can be defined as
175//! [*the quality or state of being transient*]. The `transient` crate adopts this
176//! term throughout its code and documentation to describe the relationship that a
177//! data structure has with the 0 or more lifetimes parameters it depends on, as
178//! codified by the [`Transience`] trait. More specifically, this therm refers the
179//! [variance] of a type with respect to each of its generic lifetime parameters,
180//! which is a fairly niche topic in everyday Rust programming by plays a major
181//! role in the implementation of this crate.
182//!
183//! ### Transience bounds and transitions
184//! A simplified version of this crate's functionality could be implemented by
185//! simply allowing a type `T: Transient` to be cast to, and restored from, a
186//! `dyn Any<T::Transient>` trait object. This would be sufficient in some cases,
187//! but is has a significant limitation in that two types `S` and `T` with
188//! differing `Transience` types would erase to different trait objects; the
189//! erased types `dyn Any<S::Transience>` and `dyn Any<T::Transience>` would
190//! be distinct types that could not be used interchangeably or stored together
191//! in a homogeneous container.
192//!
193//! To evade this limitation and provide maximum flexibility, the `transient::Any`
194//! trait has a bounded blanket implementation that allows a type to erase to
195//! any transience which is more (or equally) conservative than its own. For
196//! example, a type `struct S<'long>(&'long i32)` that implements `Transient`
197//! with a `Transience` of `Co<'long>` can be erased to `dyn Any<_>` with any
198//! compatible transience such as `Co<'long>`, `Co<'short>`, `Inv<'long>`, and
199//! `Inv<'short>`.
200//!
201//! #### Mixing _covariant_ and _contravariant_ types
202//! As a result of the flexibility discussed above, the following example of
203//! storing covariant and contravariant types in the same `Vec` is possible:
204//! ```
205//! use transient::*;
206//!
207//! struct CoStruct<'a>(&'a i32);
208//! unsafe impl<'a> Transient for CoStruct<'a> {
209//!     type Static = CoStruct<'static>;
210//!     type Transience = Co<'a>;
211//! }
212//!
213//! struct ContraStruct<'a>(fn(&'a i32));
214//! unsafe impl<'a> Transient for ContraStruct<'a> {
215//!     type Static = ContraStruct<'static>;
216//!     type Transience = Contra<'a>;
217//! }
218//!
219//! let value: i32 = 5;
220//! fn func(val: &i32) { dbg!(val); }
221//!
222//! // `co` could erase to `dyn Any<Co>`, but also `dyn Any<Inv>`
223//! let co = Box::new(CoStruct(&value));
224//! // `co` could erase to `dyn Any<Contra>`, but also `dyn Any<Inv>`
225//! let contra = Box::new(ContraStruct(func));
226//! // the type annotation coerces both to choose the latter
227//! let erased_vec: Vec<Box<dyn Any<Inv>>> = vec![co, contra];
228//!
229//! assert!(erased_vec[0].downcast_ref::<CoStruct>().is_some());
230//! assert!(erased_vec[1].downcast_ref::<ContraStruct>().is_some());
231//! ```
232//! Note however, that this technique is not _always_ possible; if you have a
233//! `CoStruct<'short>` and `ContraStruct<'long>`, there would be no common
234//! `Transience` for them to erase to; the first _cannot_ be lengthened to the
235//! `'long` lifetime due to its covariance, and the second _cannot_ be shortened
236//! to `'short` due to its contravariance.
237//!
238//! #### Mixing types with different numbers of lifetime parameters
239//! Type with more than one lifetime parameter can use a tuple containing a
240//! variance item for each lifetime as their `Transience`; this is discussed
241//! in-depth in the documentation for the [`Transience`] trait. Consider the
242//! following example defining two types, the first with a single lifetime
243//! and the second with two. We will choose _invariance_ for all lifetime
244//! parameters for simplicity, but when it comes to usage, a similar
245//! situation to the "mixed co- and contra-variance " example above arises;
246//! if we want to use the types together, we need to find a way to erase
247//! both types to a common `dyn Any<_>` trait object:
248//! ```
249//! use transient::*;
250//!
251//! struct OneLifetime<'a>(&'a i32);
252//! unsafe impl<'a> Transient for OneLifetime<'a> {
253//!     type Static = OneLifetime<'static>;
254//!     type Transience = Inv<'a>;
255//! }
256//!
257//! struct TwoLifetimes<'a, 'b>(&'a i32, &'b i32);
258//! unsafe impl<'a, 'b> Transient for TwoLifetimes<'a, 'b> {
259//!     type Static = TwoLifetimes<'static, 'static>;
260//!     // we use a tuple for the `Transience` that covers both lifetimes
261//!     type Transience = (Inv<'a>, Inv<'b>);
262//! }
263//!
264//! let (value1, value2) = (5, 7);
265//! // The "natural" choice would be erasing to `dyn Any<Inv>`
266//! let one = Box::new(OneLifetime(&value1));
267//! // The "natural" choice would be erasing to `dyn Any<(Inv, Inv)>`
268//! let two = Box::new(TwoLifetimes(&value1, &value2));
269//! // The trait objects would not be compatible, but `one` can actually erase
270//! // to `dyn Any<(Inv, Inv)>` as well, since adding additional components is
271//! // allowed; so let's do that:
272//! let erased_vec: Vec<Box<dyn Any<(Inv, Inv)>>> = vec![one, two];
273//! assert!(erased_vec[0].downcast_ref::<OneLifetime>().is_some());
274//! assert!(erased_vec[1].downcast_ref::<TwoLifetimes>().is_some());
275//! ```
276//!
277//! In this example, we actually could have taken the opposite approach instead
278//! by coercing the types to `dyn Any<Inv<'a>>`, which would implicitly force `'a`
279//! and `'b` to be equal. In this case that would work since 'a and 'b are indeed
280//! equal:
281//! ```
282//! # use transient::*;
283//! # struct OneLifetime<'a>(&'a i32);  
284//! # unsafe impl<'a> Transient for OneLifetime<'a> {
285//! #     type Static = OneLifetime<'static>;
286//! #     type Transience = Inv<'a>;
287//! # }
288//! # struct TwoLifetimes<'a, 'b>(&'a i32, &'b i32);
289//! # unsafe impl<'a, 'b> Transient for TwoLifetimes<'a, 'b> {
290//! #     type Static = TwoLifetimes<'static, 'static>;
291//! #     type Transience = (Inv<'a>, Inv<'b>);
292//! # }
293//! let (value1, value2) = (5, 7);
294//! let one = Box::new(OneLifetime(&value1));
295//! let two = Box::new(TwoLifetimes(&value1, &value2));
296//! // allowed because 'a == 'b
297//! let erased_vec: Vec<Box<dyn Any<Inv>>> = vec![one, two];
298//! assert!(erased_vec[0].downcast_ref::<OneLifetime>().is_some());
299//! assert!(erased_vec[1].downcast_ref::<TwoLifetimes>().is_some());
300//! ```
301//! However if `'a `was `'short` and `'b` was `'long` then the invariance would
302//! prevent them from being unified.
303//!
304//! [`TypeId`]: TypeId
305//! [`transient::Any`]: Any
306//! [`Transient`]: tr::Transient
307//! [variance]: https://doc.rust-lang.org/nomicon/subtyping.html
308//! [_subtyping and variance_]: https://doc.rust-lang.org/nomicon/subtyping.html
309//! [*the quality or state of being transient*]: https://www.merriam-webster.com/dictionary/transience
310#![deny(missing_docs, clippy::missing_safety_doc)]
311#![allow(unknown_lints, clippy::too_long_first_doc_paragraph)]
312
313pub mod any;
314pub mod transience;
315
316// intentionally non-public to avoid naming conflicts with the crate
317mod transient;
318
319#[doc(inline)]
320pub use crate::any::{Any, Downcast, TypeId};
321
322#[doc(inline)]
323pub use crate::transient::{Static, Transient};
324
325#[doc(inline)]
326pub use transience::{Co, Contra, Inv, Timeless, Transience};
327
328#[doc(inline)]
329pub use transience::{Contravariant, Covariant, Invariant, Lifetime};
330
331pub use transience::{CanRecoverFrom, CanTranscendTo};
332
333#[cfg(feature = "derive")]
334pub use transient_derive::Transient;
335
336/// Re-exports the [`Transient`] trait to enable unambiguously importing it
337/// instead of the [`Transient`][transient_derive::Transient] derive macro.
338pub mod tr {
339    pub use super::transient::{Static, Transient};
340}
341
342#[cfg(test)]
343pub mod tests;
344
345#[cfg(doctest)]
346#[doc = include_str!("../README.md")]
347struct ReadMe;