ty_tag/
lib.rs

1//! Not all types in Rust are `'static`. Meaning they can't be used with [`core::any::TypeId`].
2//! However, it is often useful to be able to name a lifetime containing type via a `'static`
3//! type. Most implementations go about this by using the same type with all the lifetimes
4//! replaced by `'static`. So for example `&'a [u8]` would be named by `&'static [u8]`. These
5//! implementations suffer from some limitations. One being that they are usually limited to
6//! a single lifetime `'a`.
7//!
8//! This crate takes a different approach to solve this issue. We introduce the idea of tags.
9//! A tag is an arbitrary type with an associated lifetime containing type. So for the `&'a [u8]`
10//! example we would have a `struct SliceU8;` with the `&'a [u8]` as an associated type.
11//! You may notice that by itself this has an issue. We somehow need to get a `'a` to actually
12//! write the `&'a [u8]` associated type. The lifetime can't live on the `SliceU8` because we
13//! need that to be `'static`.
14//!
15//! We solve this by injecting the lifetime `'a` in an operation called reification. This operation
16//! combines a tag type and some number of lifetimes into the lifetime containing type. So
17//! `SliceU8` + `'a` reifies to `&'a [u8]`. Combining this with a way to get a tag type from a
18//! lifetime containing type, we gain the ability to go full circle from a lifetime containing type
19//! to a `'static` type back to the lifetime containing type.
20//!
21//! In this crate a tag type is defined to be a type that implements both [`Tag`] and [`WithLt`].
22//! These types automatically gain the [`Reify`] operation for constructing the lifetime containing
23//! type they name. Any type can have a tag associated with it via the [`Tagged`]
24//! trait. Note, [`Tagged`] does not require the associated tag to name the `Self` type. This
25//! is a useful property but may be unintuitive.
26//!
27//! ```
28//! # #[cfg(all(feature = "macros", feature = "core"))]
29//! # fn run() {
30//! use ty_tag::{tag, TagOf, Reify, l};
31//!
32//! // Check that the tag of &str is a 'static type.
33//! is_static::<TagOf<&str>>();
34//!
35//! // Introduce an arbitrary lifetime 'a.
36//! fn with_lt<'a>() {
37//!     // Combine the tag of &str with lifetime 'a to form the &'a str lifetime containing type.
38//!     reify::<'a, TagOf<&str>, &'a str>();
39//! }
40//! with_lt();
41//!
42//! fn reify<'a, T: Reify<l!['a], Reified = U>, U>() {}
43//!
44//! fn is_static<T: 'static>() {}
45//! # }
46//! # #[cfg(all(feature = "macros", feature = "core"))]
47//! # run();
48//! ```
49//!
50//! Another concept this crate introduces is that of a tag group. Because of Rust's coherence rules
51//! we are limited in our ability to implement [`Tagged`] for types outside a user's crate. To work
52//! around this we use tag groups like [`DefaultGroup`] to allow a user to bypass the coherence
53//! rules by naming a type they control. This functionality is not used within this crate, but
54//! is designed to improve the ergonomics of other crates.
55//!
56//! ## Crate features
57#![cfg_attr(
58    feature = "document-features",
59    doc = ::document_features::document_features!()
60)]
61//!
62//! ## `no_std` Support
63//!
64//! This crate is `#![no_std]` by default, it can be used anywhere Rust can.
65//!
66//! ## Minimum Supported Rust Version
67//!
68//! Requires Rust 1.83.0.
69//!
70//! This crate follows the ["Latest stable Rust" policy](https://gist.github.com/alexheretic/d1e98d8433b602e57f5d0a9637927e0c). The listed MSRV won't be changed unless needed.
71//! However, updating the MSRV anywhere up to the latest stable at time of release
72//! is allowed.
73
74// no_std by default.
75#![cfg_attr(not(any(feature = "std", test)), no_std)]
76// Only some convenience methods of Label need unsafe.
77#![cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))]
78// On docs.rs we show the cfg requirements in the docs.
79#![cfg_attr(docsrs, feature(doc_auto_cfg))]
80// Why not? The module name repetitions does flag a few spots where the modules aren't public.
81#![warn(clippy::pedantic)]
82#![allow(clippy::module_name_repetitions)]
83
84// If the alloc feature is enabled we need to depend on the alloc crate.
85#[cfg(any(feature = "alloc", test))]
86extern crate alloc;
87
88pub mod lifetime_list;
89
90// We only need this module if one of the features is enabled.
91#[cfg(any(feature = "core", feature = "alloc", feature = "std"))]
92pub mod external_tags;
93
94pub mod modifiers;
95
96mod tag_type_id;
97
98use lifetime_list::ops::{FirstNLifetimes, Prefix, ToHoleList};
99use lifetime_list::{HoleList, LifetimeList, L0};
100pub use tag_type_id::TagTypeId;
101/// Create a tag for a type.
102///
103/// Tags use the same syntax as type aliases.
104///
105/// ```
106/// use ty_tag::tag;
107///
108/// struct MyType<'a, T: 'a, const N: usize>(&'a [T; N]);
109///
110/// #[tag]
111/// type MyTag<'a, T: 'a, const N: usize> = MyType<'a, T, N>;
112/// ```
113///
114/// `MyTag` will be converted to a type without the lifetime parameter `'a`.
115/// This type can then be used anywhere a [`Tagged`] implementing type is needed.
116/// The tag will reify to `&'a [T; N]` when the lifetime `'a` is added.
117///
118/// ## Option `remote`
119///
120/// The `remote` option can be included to remove the implementation of [`Tagged`]
121/// for the type in the [`DefaultGroup`] group. This is needed if the type
122/// is not considered local under the orphan rules.
123///
124/// ```
125/// use ty_tag::tag;
126///
127/// // error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
128/// // #[tag]
129/// // type Tag = u8;
130///
131/// #[tag(remote)]
132/// type Tag = u8;
133/// ```
134///
135/// ## Option `group = ...`
136///
137/// Option to tag the type in a group. This can be used to implement [`Tagged`] for a type
138/// even if it's not local. The group type can be anything; There are no required traits for it.
139///
140/// ```
141/// use ty_tag::tag;
142///
143/// struct MyGroup;
144///
145/// struct MyType;
146///
147/// #[tag(remote, group = MyGroup)]
148/// type Tag = MyType;
149/// ```
150///
151/// ## Attribute `itself`
152///
153/// By default, generic types will use [`Tagged`] to lookup a tag for them. However, this may not
154/// always be wanted. Instead, if the generic is `'static`, then `#[itself]` can be added to the
155/// generic type to use the generic type as the tag directly.
156///
157/// ```
158/// use ty_tag::{tag, TagOf};
159///
160/// struct MyType<T>(T);
161///
162/// struct NotTagged;
163///
164/// #[tag]
165/// type Tag<#[itself] T: 'static> = MyType<T>;
166///
167/// let _example: TagOf<Tag<NotTagged>>;
168/// ```
169#[cfg(feature = "macros")]
170pub use ty_tag_macros::tag;
171
172/// Get the associated [`Tagged::Tag`] of `T`.
173pub type TagOf<T, Group = DefaultGroup> = <T as Tagged<Group>>::Tag;
174
175// This is used by the tag macro to prevent constructing a tag struct.
176#[doc(hidden)]
177pub enum __ {}
178
179// Needed for the tag macro to work within this crate.
180extern crate self as ty_tag;
181
182/// The default tag group.
183///
184/// All tag types are themselves always tagged within this default group.
185/// Most APIs will default to this group.
186pub struct DefaultGroup(());
187
188/// `Self` is tagged in `Group`.
189///
190/// Prefer using the [`tag`] macro over implementing this manually.
191/// When implementing this trait, it is recommended to implement for all `Group: ?Sized`.
192/// This is possible when the `Self` type is local. When it is not local then a specific
193/// local group type can be used instead.
194///
195/// Instead of using this trait directly, [`TagOf`] can be used to get the `Tag` associated type.
196/// Additionally, most uses should bound by [`Reify`] instead. `Reify` includes [`Tagged`] as
197/// a super trait.
198pub trait Tagged<Group: ?Sized = DefaultGroup> {
199    /// The tag for `Self` within the group `Group`.
200    type Tag: Tag;
201}
202
203/// A tag for a type.
204///
205/// Tags can be for types with an arbitrary number of lifetimes.
206/// The number of lifetimes in the tagged type is described by the
207/// [`Tag::NeededLifetimes`] associated type.
208///
209/// For a tag to be complete, it needs to implement [`WithLt`] which defines
210/// which type the tag is for. Once a tag implements both [`Tag`] and [`WithLt`]
211/// then it will also implement [`Reify`] which can be used to reify the tag into
212/// the type it is for.
213///
214/// Tags are also required to tag themselves within the [`DefaultGroup`] group.
215/// This requirement allows for some extra ergonomics.
216pub trait Tag: Sized + Tagged<DefaultGroup, Tag = Self> + 'static {
217    /// The needed lifetimes by the tagged type.
218    type NeededLifetimes: HoleList;
219}
220
221/// The reified type for a tag.
222///
223/// All tag types are required to implement this trait to be complete.
224/// Once a tag implements both [`Tag`] and [`WithLt`]
225/// then it will also implement [`Reify`] which can be used to reify the tag into
226/// the type it is for.
227///
228/// The `L` generic allows an arbitrary number of lifetime to be used to construct the
229/// [`WithLt::Reified`] type.
230// I wish the commented out bound was here but it causes a infinite loop resolving the Reify
231// bounds.
232pub trait WithLt<L: LifetimeList>: Tag {
233    /// The type tagged.
234    type Reified: ?Sized;
235}
236
237/// Type level operation to reify a tag into it's associated type.
238///
239/// **Do not** attempt to implement this manually.
240/// This trait is implemented on anything that implements [`Tagged`] with a valid tag.
241///
242/// The `L` generic is some list of lifetimes to combine with the tag to form the reified type.
243/// By default, it is a list with no lifetimes.
244/// The lifetime list length does not need to match that of the tag. The first N lifetimes
245/// of the list will be used in the reify operation.
246///
247/// For most uses, the default `Group` of [`DefaultGroup`] will be enough. In cases where
248/// `Self` is expected to be tagged by another group it can be changed.
249///
250/// The extra associated types are provided to reduce the needed trait
251/// bounds for some usages. They fully connect the tag with the reified
252/// type in a self consistent way.
253///
254/// # Examples
255///
256/// ```
257/// # #[cfg(all(feature = "macros", feature = "core"))]
258/// # fn run() {
259/// use ty_tag::{tag, TagOf, Reify, l};
260///
261/// fn with_lt<'a>() {
262///     // Combine the tag of &str with lifetime 'a to form the &'a str lifetime containing type.
263///     reify::<'a, TagOf<&str>, &'a str>();
264///
265///     // The type itself also (usually) works with Reify.
266///     reify::<'a, &'static str, &'a str>();
267///
268///     // Even the lifetime containing form can be used.
269///     reify::<'static, &'a str, &'static str>();
270/// }
271/// with_lt();
272///
273/// fn reify<'a, T: Reify<l!['a], Reified = U>, U>() {}
274/// # }
275/// # #[cfg(all(feature = "macros", feature = "core"))]
276/// # run();
277/// ```
278///
279pub trait Reify<L: LifetimeList = L0, Group: ?Sized = DefaultGroup>:
280    Tagged<Group, Tag = Self::UsedTag>
281{
282    /// The reified type.
283    ///
284    /// This is taken from the tag's [`WithLt`] implementation.
285    type Reified: ?Sized;
286
287    /// The lifetimes used to reify the type.
288    ///
289    /// This will be some prefix of the full `L` list of lifetimes.
290    type Lifetimes: LifetimeList + ToHoleList<HoleList = Self::UsedNeededLifetimes> + Prefix<L>;
291
292    /// The tag used for the reification.
293    ///
294    /// [`Reify`] is implemented on anything with an implementation of [`Tagged`].
295    /// This type is the [`Tagged::Tag`].
296    type UsedTag: Tag<NeededLifetimes = Self::UsedNeededLifetimes>
297        + WithLt<Self::Lifetimes, Reified = Self::Reified>;
298
299    /// The needed lifetimes from the tag.
300    ///
301    /// This is the [`Tag::NeededLifetimes`] of [`Tagged::Tag`]. It is also the same length as the
302    /// [`Reify::Lifetimes`] list.
303    type UsedNeededLifetimes: FirstNLifetimes<Self::Lifetimes, Output = Self::Lifetimes>
304        + FirstNLifetimes<L, Output = Self::Lifetimes>
305        + HoleList;
306}
307
308/// [`Reify`] but with a [`Sized`] `Reified`.
309///
310/// Because a `Sized` time is often expected this trait can be used instead
311/// to reduce the amount of bounds needed at the usage site.
312///
313/// This trait is automatically implemented for all types it can be.
314pub trait ReifySized<L: LifetimeList, Group: ?Sized = DefaultGroup>:
315    Reify<L, Group, Reified = Self::SizedReified>
316{
317    /// Same as [`Reify::Reified`].
318    type SizedReified;
319}
320
321impl<L: LifetimeList, Group: ?Sized, T> ReifySized<L, Group> for T
322where
323    T: Reify<L, Group>,
324    T::Reified: Sized,
325{
326    type SizedReified = <T as Reify<L, Group>>::Reified;
327}
328
329/// Types that reify to themselves.
330///
331/// Some tags have a special property; They are tagged and that tag reifies by [`Reify`] to
332/// that same type. This makes that one tag unique. As a result that tag can be used as a true
333/// unique name of the type containing lifetimes.
334///
335/// This trait is automatically implemented for all types it can be.
336pub trait ReifySelf<L: LifetimeList = L0>: Reify<L, Reified = Self> {}
337
338impl<L: LifetimeList, T: ?Sized> ReifySelf<L> for T where T: Reify<L, Reified = Self> {}
339
340// Some helpers for the impl below.
341type NeededLt<T> = <T as Tag>::NeededLifetimes;
342type TakeLt<H, L> = <H as FirstNLifetimes<L>>::Output;
343
344// This is the core impl of the crate.
345//
346// It combines anything implementing `Tagged` and a lifetime list of arbitrary length
347// to form the reified type named by the tag used in the tagged impl.
348//
349// Doing the FirstNLifetimes stuff here removes a lot of extra bounds from the WithLt trait impls.
350impl<T, L, Group: ?Sized> Reify<L, Group> for T
351where
352    // T is some type with an associated tag. The reified type may or may not actually be T.
353    // In any case the lifetimes T may contain will be replaced by the ones in L.
354    T: ?Sized + Tagged<Group>,
355
356    // A list of lifetime to use to reify the type named by the tag.
357    L: LifetimeList,
358
359    // We need to be able to take the lifetimes needed by the tag from L.
360    NeededLt<T::Tag>: FirstNLifetimes<L>,
361
362    // This bound makes it so we can use the subset of lifetimes from the above abound
363    // to do the same thing. This bound should be trivial in practice since we have strong
364    // knowledge of what L could be.
365    NeededLt<T::Tag>:
366        FirstNLifetimes<TakeLt<NeededLt<T::Tag>, L>, Output = TakeLt<NeededLt<T::Tag>, L>>,
367
368    // The tag needs to have WithLt with the lifetime list the tag said it needed.
369    T::Tag: WithLt<TakeLt<NeededLt<T::Tag>, L>>,
370{
371    // Reify the type using the subset of L.
372    type Reified = <T::Tag as WithLt<TakeLt<NeededLt<T::Tag>, L>>>::Reified;
373
374    // The subset of L that was used.
375    type Lifetimes = TakeLt<NeededLt<T::Tag>, L>;
376
377    // The tag.
378    type UsedTag = T::Tag;
379
380    // The lifetimes the tag needed.
381    type UsedNeededLifetimes = NeededLt<T::Tag>;
382}