pub unsafe trait Transient: Sized {
type Static: 'static;
type Transience: Transience;
// Provided methods
fn static_type_id(&self) -> TypeId { ... }
fn erase<'a>(self: Box<Self>) -> Box<dyn Any<Self::Transience> + 'a>
where Self: 'a { ... }
fn erase_ref<'a>(&self) -> &(dyn Any<Self::Transience> + 'a)
where Self: 'a { ... }
fn erase_mut<'a>(&mut self) -> &mut (dyn Any<Self::Transience> + 'a)
where Self: 'a { ... }
}Expand description
Unsafe trait defining the lifetime-relationships of a potentially non-'static
type so that it can be safely erased to dyn Any. This trait can
be derived using the Transient derive macro.
§Implementing the Transient trait
Implementing the Transient trait only requires the definition of two
associated types: the Static type, and the
Transience. The requirements for safely choosing
these types are explaining in the following subsections.
§The Static associated type
This type should be the same as the Self type but with all lifetime
parameters replaced by 'static. See the next section
for numerous examples and a discussion of the few non-trivial cases such
types with non-'static generic type parameters.
§The Transience associated type
This type must be a Transience implementation that properly captures the
variance of the type with respect to each of its lifetime parameters. The
safe implementation of this types is a bit more nuanced than the Static
type, and differs depending on the number of lifetime parameters for the type
as laid out in the following subsections.
§Types with 0 lifetime parameters
These types are the easiest to implement and use since they contain no borrowed
data to worry about, and can simply use the unit type () (or the Timeless
type alias) as their Transience.
§Types with exactly 1 lifetime parameter
For a type with a single lifetime parameter 'a, there are 3 main options
which correspond to the standard variances that a type can have:
Inv<'a>– this type declares that the implementing type is invariant with respect to'a, which means that the compiler can neither shorten nor length its lifetime freely. This is the most conservative form of variance, and can safely be chosen for theTransienceof any single-lifetime type. However, this variance is the least flexible when it comes to usage; in a world were&'a strwas considered invariant you would not be allowed to pass a&'static strto a function expecting&'short str, even though the former should clearly be more capable than the latter.Co<'a>– this type declares that the implementing type is covariant with respect to'a, which means that the compiler can safely shorten its lifetime as needed, but cannot lengthen it. Most types exhibit this variance and can use it for theirTransience(such as&'a strdiscussed above), but using it in the few cases where it does not apply could result in undefined behavior (and so covariance cannot be the default). Notable exceptions from covariant behavior include the argument of a function pointer (a type containing anfn(&'a str)is contravariant w.r.t.'a) and the pointee of a mutable reference (&'a mut &'b stris invariant w.r.t.'b, although it is still covariant w.r.t.'a).Contra<'a>– this type declares that the implementing type is contravariant with respect to'a, which means that the compiler can safely lengthen its lifetime as needed, but cannot shorten it. This is the least common variance so I won’t discuss it in depth, but the main example of contravariance is the relationship between a function and the lifetime parameters of its arguments (such asfn(&'a str)as mentioned above.
As a side note, a 1-tuple containing Inv, Co, or Contra can
also be used for the Transience of a single-lifetime type (subject
to the same rules as above), but this is typically less convenient
when it comes to usage.
§Types with more than 1 lifetime parameter
Choosing a Transience for a type with multiple lifetime parameters
is really no harder than for a single-lifetime types, since the same
variances discussed above can simply be composed as tuples with an
independent element corresponding to each lifetime. For example,
any type with two lifetimes 'a and 'b can safely choose
(Inv<'a>, Inv<'b>) as its Transience since the independent
choices of Inv<'a> and Inv<'b> are always safe. For a type like
&'a mut &'b str which is is covariant w.r.t. to 'a but
invariant w.r.t. b, the Transience could be defined as
(Co<'a>, Inv<'b>). Just make sure to include a tuple element for
every lifetime in the struct (choosing Inv<'_> for the variance
when unsure), since any excluded lifetimes will be unbounded and can
lead to undefined behavior.
Note that this pattern of composing tuples should in theory hold
for any number of lifetimes (i.e. a type with 100 lifetimes using
a 100-tuple of variances), but in practice the Transience trait
is only actually implemented for 1-, 2-, 3-, and 4-tuples. If you
need more than this feel free to submit an issue requesting it.
§Examples
Note: The following examples demonstrate how to correctly implement this trait. For practical usage examples, see the crate documentation.
§Static types
The simplest case of implementing this trait is for a struct that is already
'static (i.e. it only contains owned data and/or 'static references. For
such a struct, the Static type can simply be Self, and the Transience
type can be the unit type () (or the type alias transient::Timeless):
use transient::Transient;
struct S {
name: &'static str,
value: i32
}
unsafe impl Transient for S {
type Static = Self;
type Transience = ();
}Of course, this crate would not be necessary in this case, but it is still
worth mentioning that 'static types are indeed supported.
§Types with a single lifetime parameter
The next simplest case would be a struct with a single lifetime parameter and no generic type parameters:
use transient::{Transient, Inv};
struct S<'a> {
value: &'a str,
}
// This could also be derived
unsafe impl<'a> Transient for S<'a> {
type Static = S<'static>;
type Transience = Inv<'a>;
}§Types with multiple lifetime parameters
Now consider a struct that borrows 2 string slices with independent lifetime parameters (which is currently not supported by the derive macro):
struct TwoRefs<'a, 'b> {
a: &'a str,
b: &'b str,
}There are several options for how to safely implement the Transient trait
for such a type. The most versatile option is to follow the same pattern as
for the single-lifetime example, but to use a tuple for the Transience
type that contains a separate Transience for each lifetime:
unsafe impl<'a, 'b> Transient for TwoRefs<'a, 'b> {
type Static = TwoRefs<'static, 'static>;
type Transience = (Inv<'a>, Inv<'b>);
}Another option is to establish a relationship between the lifetimes that allows
a most conservative Transience to be unambiguously identified for use in the impl:
// 'b outlives 'a -> choose 'a for the trait
unsafe impl<'a, 'b: 'a> Transient for TwoRefs<'a, 'b> {
type Static = TwoRefs<'static, 'static>;
type Transience = Inv<'a>;
}This can make using the dyn transient::Any trait object more convenient
in some cases, but will result in the lifetime of the restored type being
truncated to the chosen lifetime.
However, choosing either 'a or 'b for the trait without declaring
bounds to justify the decision is unsound and may lead to undefined
behaviour.
§Generic type parameters
Generic type parameters are also supported; however, there is one extra
consideration to keep in mind. Since the Static associated type on the
Transient trait is bounded by 'static, any generics parameters that
appear in this type must also be 'static. The easiest way to meet this
condition is to directly bound the type parameters by 'static for the
impl block, as shown in the following example; however, there is workaround
for cases where this is not acceptable, which will be shown next.
For the case where the type parameters can be 'static:
use transient::{Transient, Inv};
// This struct is generic over type `T`, which might not be `'static`
struct S<'a, T> {
value: &'a T,
}
// By adding `T: 'static` to the impl block we can satisfy the `'static`
// requirement, at the cost of limiting the scope of the impl
unsafe impl<'a, T: 'static> Transient for S<'a, T> {
type Static = S<'static, T>;
type Transience = Inv<'a>;
}If you need to support cases where T is not necessarily 'static, another
option is to bound T by Transient itself and then using T::Static in
the Static type for the impl:
use transient::{Transient, Inv};
struct S<'a, T> {
value: &'a T,
}
unsafe impl<'a, T: Transient> Transient for S<'a, T> {
type Static = S<'static, T::Static>;
type Transience = Inv<'a>;
}Of course, this limits the impl to types where Transient either is or
can be implemented. If you need to support external types for which you
cannot implement Transient due to the orphan rule, your only options
would be to wrap T in a newtype struct for which you can implement
Transient, or request that the impl be added by this crate or the type’s
crate.
§Safety
- The
Staticassociated type must be the same type as the implementing type, but with all lifetime parameters replaced by'staticand any non-'static-bounded type parametersTreplaced byT::Static(for which they must be bounded byTransient). Specifically, the type must have the same layout asSelfso thatstd::mem::transmuteand raw pointer casts pointer casts between them are sound, and thestd::any::TypeIdof theStatictype must correctly identify theSelftype. - The
Transienceassociate type must include a component for each lifetime parameter that accurately (or more conservatively) captures theSelftype’s variance with respect to it, as detailed in the documentation for theTransiencetrait and demonstrated in the sections above. For a'statictype this should be(), for a single-lifetime type it should beInv<'a>as a safe default orCo<'a>/Contra<'a>if appropriate, and for a multi-lifetime type this should be(Inv<'a>, Inv<'b>, ...)as a safe default withCoandContraoptionally substituted where appropriate. ChoosingCoorContrafor any lifetime parameter without respecting the rules of Subtyping and Variance, or excluding any independent lifetime parameter from theTransienceis undefined behavior.
Required Associated Types§
sourcetype Static: 'static
type Static: 'static
Same as Self but with all lifetime parameters replaced by 'static.
See the Transient trait’s docstring for examples and a discussion of
the considerations necessary for defining the type in various cases.
§Safety
This must be equivalent to the implementing type, such that matching its
TypeId to that of a dyn Any trait objects is sufficient justification
for performing a std::mem::transmute or raw pointer cast to it
(excluding lifetime considerations).
sourcetype Transience: Transience
type Transience: Transience
Type reflecting the variances of Self with respect to its lifetime parameters.
See the Transience docstring for a thorough explanation and examples.
§Safety
This type must sufficiently capture the variance characteristics of the type with respect to every one of its lifetime parameters as discussed in the documentation for the trait.
Provided Methods§
sourcefn static_type_id(&self) -> TypeId
fn static_type_id(&self) -> TypeId
Obtain the unique identifier assigned by the compiler to the
Static variant of the type.
See the docstring for the TypeId type for a discussion of the subtle
differences from the related std::any::TypeId, and the Any::type_id
method for an explanation of why this method is necessary.
See TypeId::of_val for an alternate method of obtaining the TypeId
for a value with a concrete type.
sourcefn erase<'a>(self: Box<Self>) -> Box<dyn Any<Self::Transience> + 'a>where
Self: 'a,
fn erase<'a>(self: Box<Self>) -> Box<dyn Any<Self::Transience> + 'a>where
Self: 'a,
Convenience method to cast Box<Self> to Box<dyn Any<_>> with the
transience defined in the Transient implementation.
This shorthand can be useful since the default dyn Any only works for
'static types, so Transient types would need to import the appropriate
Transience type (such as Co) and explicitly specify
dyn Any<Co> even for trivial usages (although using dyn Any<_> and
letting type-inference fill-in-the-blank will also work in some cases).
sourcefn erase_ref<'a>(&self) -> &(dyn Any<Self::Transience> + 'a)where
Self: 'a,
fn erase_ref<'a>(&self) -> &(dyn Any<Self::Transience> + 'a)where
Self: 'a,
Convenience method to cast &Self to &dyn Any<_> with the
transience defined in the Transient implementation.
sourcefn erase_mut<'a>(&mut self) -> &mut (dyn Any<Self::Transience> + 'a)where
Self: 'a,
fn erase_mut<'a>(&mut self) -> &mut (dyn Any<Self::Transience> + 'a)where
Self: 'a,
Convenience method to cast &mut Self to &mut dyn Any<_> with the
transience defined in the Transient implementation.