Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add associated transience types #8

Merged
merged 5 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ pub use crate::transient::{Static, Transient};
#[doc(inline)]
pub use transience::{Co, Contra, Inv, Timeless, Transience};

#[doc(inline)]
pub use transience::{Contravariant, Covariant, Invariant, Lifetime};

pub use transience::{CanRecoverFrom, CanTranscendTo};

#[cfg(feature = "derive")]
Expand Down
176 changes: 146 additions & 30 deletions src/transience.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ use std::marker::PhantomData;
/// undefined behavior if chosen incorrectly. To override this default, the
/// [`Transience` associated type] can be set in the type's `Transient`
/// implementation; if using the derive macro, this corresponds to including
/// the `#[r#unsafe(covariant)]` attribute.
/// the `#[covariant]` or `#[contravariant]` attribute.
///
/// To maximize the flexibility of this crate's functionality, transitions
/// between compatible `Transiences` are supported. See the documentation for
/// the [`CanTranscendTo`] and[ `CanRecoverFrom`] traits for an introduction
/// to these transitions and when they are used, as well as a discussion of
/// why certain transitions are allowed while others are forbidden.
///
///
/// ## Valid transitions table
/// The following table summarizes the allowable transitions that can be
/// made between (single-lifetime) transiences, where the _rows_ represent
Expand All @@ -34,7 +33,7 @@ use std::marker::PhantomData;
/// next section):
///
/// <table style="width:80%">
/// <tr style="font-size: 12px;">
/// <tr style="font-size: 12px;background-color:white">
/// <th style="width:1%;"> </th>
/// <th> <code>Inv&lt'short&gt</code> </th>
/// <th> <code>Inv&lt'long&gt</code> </th>
Expand Down Expand Up @@ -122,24 +121,31 @@ use std::marker::PhantomData;
/// For example, a type `&'long i32` with a transience of `Co<'long>` (middle row)
/// can be safely erased to the trait object `dyn Any<Co<'short>>` (3rd column) by
/// shortening its lifetime. However, when we want to
/// [`downcast`] the opaque trait object back into its concrete type, we _cannot_ just
/// assume (at least under the implementation of this crate) that we can safely recover
/// a `'long` lifetime from the `'short` lifetime of the trait object;
/// instead, we can only safely recover `&'short i32` from the `dyn Any<Co<'short>>`.
/// As a result, even though the underlying reference should actually be valid for the
/// `'long` lifetime, we end up with a reference that the borrow-checker only believes
/// to be valid for the `'short` lifetime. This may seem silly if you happen to be
/// dealing with a case where _you know_ what you are working with and that the
/// `'long` lifetime _is_ valid; but the whole point of type-erasure is that
/// sometimes you _dont_ know/care, or you don't want to compiler to know/care,
/// and sometimes ignorance has a cost. On the bright side, if you _really are_
/// confident that `'long` is valid, you can `unsafe`-ly fix it using hacks like
/// [`std::mem::transmute`] and raw pointer casts (at your own risk, of course).
/// [`downcast`] the opaque trait object back into its concrete type, we _cannot_
/// assume that we can safely recover
/// a `'long` lifetime from the `'short` lifetime of the trait object. The only truly
/// safe solution is to avoid downcasting to a shorter lifetime - just as if you were
/// working with references to the type itself.
///
/// # Safety
/// The `Inverse` associated types must be defined appropriately to reflect the
/// _opposite_ variance behavior with respect to the same lifetime parameter(s).
/// Similarly, the `Invariant` associated type must be defined as an _invariant_
/// behavior with respect to the same lifetime parameter(s). Note the this does
/// _not_ apply to the `Timeless` type, which has no lifetime relationships and
/// may thus use `Timeless` for both associated types.
///
/// [`Transience` associated type]: Transient::Transience
/// [variance]: https://doc.rust-lang.org/nomicon/subtyping.html
/// [`downcast`]: crate::Downcast::downcast
pub trait Transience: Transient + CanTranscendTo<Self> + CanRecoverFrom<Self> {}
pub unsafe trait Transience:
Transient + CanTranscendTo<Self> + CanRecoverFrom<Self>
{
/// The inverse of this transience. `Co` <=> `Contra`, and `Inv` <=> `Inv`.
type Inverse;
/// The invariant version of this transience. `Co`, `Contra` => `Inv`.
type Invariant;
}

/// Unsafe marker trait indicating that the implementing [`Transience`] can
/// safely upcast to the parameterizing `Transience`.
Expand Down Expand Up @@ -237,12 +243,16 @@ pub unsafe trait CanRecoverFrom<From> {}
/// this transience by default so that it can mimic the simplicity of the
/// [`std::any::Any`] trait in the simple case of `'static` types.
pub type Timeless = ();
impl Transience for Timeless {}

unsafe impl Transience for Timeless {
type Inverse = ();
type Invariant = ();
}

/// Used to declare an [_invariant_] relationship between a type and its lifetime
/// parameter.
///
/// An *invariant* type is one for which the compiler cannot safely assume that
/// An [_invariant_] type is one for which the compiler cannot safely assume that
/// its lifetime may be shortened *or* lengthened (e.g. `'b` in `&'a mut &'b T`).
/// Such a type must therefore match the expected lifetime exactly when passed to
/// a function.
Expand All @@ -253,19 +263,22 @@ impl Transience for Timeless {}
#[derive(Clone, Copy, Debug)]
pub struct Inv<'a>(PhantomData<fn(&'a ()) -> &'a ()>);

impl<'a> Transience for Inv<'a> {}
unsafe impl<'a> Transience for Inv<'a> {
type Inverse = Inv<'a>;
type Invariant = Inv<'a>;
}

unsafe impl<'a> Transient for Inv<'a> {
type Static = Inv<'static>;
type Transience = Self;
}

/// Used to declare an [_covariant_] relationship between a type and its lifetime
/// Used to declare a [_covariant_] relationship between a type and its lifetime
/// parameter.
///
/// A *covariant* type is one for which the compiler can safely *shorten* its
/// A [_covariant_] type is one for which the compiler can safely *shorten* its
/// lifetime parameter as needed when passing it to a function; for example,
/// `&'a T` is *covariant* w.r.t. `'a`, so `&'static str` can be used where
/// `&'a T` is *covariant* w.r.t. `'a`, so `&'long str` can be used where
/// `&'short str` is expected.
///
/// See the [`Transience`] documentation for more information.
Expand All @@ -274,34 +287,134 @@ unsafe impl<'a> Transient for Inv<'a> {
#[derive(Clone, Copy, Debug)]
pub struct Co<'a>(PhantomData<&'a ()>);

impl<'a> Transience for Co<'a> {}
unsafe impl<'a> Transience for Co<'a> {
type Inverse = Contra<'a>;
type Invariant = Inv<'a>;
}

unsafe impl<'a> Transient for Co<'a> {
type Static = Co<'static>;
type Transience = Self;
}

/// Used to declare an [_contravariant_] relationship between a type and its lifetime
/// Used to declare a [_contravariant_] relationship between a type and its lifetime
/// parameter.
///
/// A *contravariant* type is one for which the compiler can safely *lengthen*
/// A [_contravariant_] type is one for which the compiler can safely *lengthen*
/// its lifetime parameter as needed when passing it to a function; for example,
/// `fn(&'a str)` is *contravariant* w.r.t. `'a`, so `fn(&'short str)` can be
/// used where `fn(&'static str)` is expected.
/// used where `fn(&'long str)` is expected.
///
/// See the [`Transience`] documentation for more information.
///
/// [_contravariant_]: https://doc.rust-lang.org/nomicon/subtyping.html
#[derive(Clone, Copy, Debug)]
pub struct Contra<'a>(PhantomData<fn(&'a ())>);

impl<'a> Transience for Contra<'a> {}
unsafe impl<'a> Transience for Contra<'a> {
type Inverse = Co<'a>;
type Invariant = Inv<'a>;
}

unsafe impl<'a> Transient for Contra<'a> {
type Static = Contra<'static>;
type Transience = Self;
}

/// Type alias that can be used in the [`Transience`] of a type to declare that it is
/// [_covariant_] with respect to a [`Transient`] type parameter.
///
/// This is analogous to the [`Co<'a>`][Co] type that is used to declare a covariant
/// relationship with respect to a lifetime parameter.
///
/// As discussed in its safety docs, the [`Transient`] trait requires an implementing
/// type to declare its temporal relationships in the [`Transience` associated type].
/// This most naturally refers to its variance with respect to its _lifetime_ parameters,
/// but it is _critical_ to account for that of its _type_ parameters as well when they
/// are allowed to be non-`'static`. This type, as well as the related [`Invariant`] and
/// [`Contravariant`] types, may be used to declare the variance of this relationship
/// by using it, or a tuple including it, as the `Transience`.
///
/// # Example
/// Consider the following type, which is _covariant_ with respect to both the
/// lifetime parameter `'a` and the type parameter `T`:
/// ```
/// struct S<'a, T>(&'a [T]);
/// ```
///
/// If we wanted to provide a `Transient` implementation for this type that allows
/// the type parameter to be non-`'static`, we can use this `Covariant<T>` type
/// alias as a component in the `Transience` associated type:
/// ```
/// # struct S<'a, T>(&'a [T]);
/// use transient::{Transient, Co, Covariant};
///
/// unsafe impl<'a, T> Transient for S<'a, T>
/// where
/// T: Transient
/// {
/// type Static = S<'static, T::Static>;
/// type Transience = (Co<'a>, Covariant<T>);
/// }
/// ```
///
/// When a concrete type of `&'b i32` is substituted for `T`, this impl expands to
/// the following which properly accounts for the lifetime `'a`, as well as the
/// lifetime `'b` that was hidden inside of the generic parameter `T`:
/// ```
/// # struct S<'a, T>(&'a [T]);
/// # use transient::{Transient, Co, Covariant};
/// unsafe impl<'a, 'b> Transient for S<'a, &'b i32> {
/// type Static = S<'static, &'static i32>;
/// type Transience = (Co<'a>, Co<'b>);
/// }
/// ```
///
/// [`Static` associated type]: Transient::Transience
/// [`Transience` associated type]: Transient::Transience
/// [_covariant_]: https://doc.rust-lang.org/nomicon/subtyping.html
pub type Covariant<T> = <T as Transient>::Transience;

/// Type alias that can be used in the [`Transience`] of a type to declare that it is
/// [_contravariant_] with respect to a [`Transient`] type parameter.
///
/// This is analogous to the [`Contra<'a>`][Contra] type that is used to declare a
/// contravariant relationship with respect to a lifetime parameter.
///
/// See the [`Covariant`] type for more information, including a usage example.
///
/// [_contravariant_]: https://doc.rust-lang.org/nomicon/subtyping.html
pub type Contravariant<T> = <<T as Transient>::Transience as Transience>::Inverse;

/// Type alias that can be used in the [`Transience`] of a type to declare that it is
/// [_invariant_] with respect to a [`Transient`] type parameter.
///
/// This is analogous to the [`Inv<'a>`][Inv] type that is used to declare an invariant
/// relationship with respect to a lifetime parameter.
///
/// See the [`Covariant`] type for more information, including a usage example.
///
/// [_invariant_]: https://doc.rust-lang.org/nomicon/subtyping.html
pub type Invariant<T> = <<T as Transient>::Transience as Transience>::Invariant;

/// Type alias that can be nested within the [`Covariant`], [`Contravariant`], or
/// [`Invariant`] type to declare the [_variance_] of a [`Transient`] struct with
/// respect to a lifetime parameter.
///
/// Using this type is purely for aesthetics, and reduces to one of the fundamental
/// [`Transience`] types:
///
/// - `Covariant<Lifetime<'a>>` is equivalent to [`Co<'a>`]
/// - `Contravariant<Lifetime<'a>>` is equivalent to [`Contra<'a>`]
/// - `Invariant<Lifetime<'a>>` is equivalent to [`Inv<'a>`]
///
/// Using `Co`, `Contra`, or `Inv` directly should usually be preferred, but some users
/// may find that this type increases clarity when use alongside type parameter variances
/// such as in the `Transience` tuple `(Covariant<Lifetime<'a>>, Covariant<T>>)`
///
/// [_variance_]: https://doc.rust-lang.org/nomicon/subtyping.html
pub type Lifetime<'a> = PhantomData<&'a ()>;

// ************************************************************************* //
// ************************* SAFETY-CRITICAL LOGIC! ************************ //
// ************************************************************************* //
Expand Down Expand Up @@ -413,10 +526,13 @@ impl_scalar_to_tuples! {
macro_rules! impl_equal_tuples {
{ $( ($($src:ident,)*) => ($($dst:ident,)*) );* $(;)? } => {
$(
impl<$($src),*> Transience for ($($src),*,)
unsafe impl<$($src),*> Transience for ($($src),*,)
where
$( $src: Transience ),*
{}
{
type Inverse = ($(<$src as Transience>::Inverse),*,);
type Invariant = ($(<$src as Transience>::Invariant),*,);
}
unsafe impl<$($src),*> Transient for ($($src),*,)
where
$( $src: Transience ),*
Expand Down
5 changes: 5 additions & 0 deletions src/transient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,11 @@ mod std_impls {
type Transience = T::Transience;
}

unsafe impl<T: Transient> Transient for std::marker::PhantomData<T> {
type Static = std::marker::PhantomData<T::Static>;
type Transience = T::Transience;
}

impl Static for Box<dyn StdAny> {}

unsafe impl<'a> Transient for &'a dyn StdAny {
Expand Down
Loading