Using "generic generics" or HKTs or whatever you wanna name it, it is indeed possible to split a 'lt
-infected type into 'lt
, and the type without the lifetime:
&'lt str ~ ('lt, HKT!(<'r> = &'r str))
with that second element being 'static
and thus having a queryable TypeId
.
- (We could even further generalize this to multiple lifetimes).
The problem then, becomes a lack of injectivity w.r.t. this split for, e.g., certain 'static
types:
&'static str ~ ('static, HKT!(<'r> = &'r str))
// but also
&'static str ~ ('static, HKT!(<'r> = &'static str))
// ^ or 'whatever
Which definitely illustrates that the one trying to query the TypeId
ought to be providing some choice of that "lifetime-deprived" TypeId
witness (that is, if your starting point was some non-: 'static
<T>
type in scope; you'd have to require that <'lt, G>
be given (whereG : HKT, G::_<'lt> = T
). That is:
trait Example { // no `Self : 'static` bound!
fn hkt_type_id<'lt, G : HKT>()
-> TypeId
where
G : 'static, // to get the TypeId
// `Self ~ ('lt, G)` i.e., `G<'lt> = Self`.
G::__<'lt> : Is<EqTo = Self>,
{
TypeId::of::<G>()
}
}
Getting lifetime-infected Any
from this idea is then easy:
-
unsafe
trait Any1<'lt> : 'lt {
fn type_id(&self) -> TypeId;
}
impl dyn Any1<'lt> {
fn coerce<T : 'static + HKT>(value: Box<T::__<'lt>>)
-> Box<Self>
{
#[repr(transparent)]
struct Helper<'lt, T : HKT>(
T::__<'lt>,
);
impl<'lt, T : 'static + HKT> Any1<'lt> for Helper<'lt, T> {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
let value: Box<Helper<'lt, T>> = unsafe {
// Safety: `repr(transparent)` layout.
::core::mem::transmute(value)
};
value
}
fn is<T : 'static + HKT>(&self)
-> bool
{
self.type_id() == TypeId::of::<T>()
}
fn downcast<T : 'static + HKT>(self: Box<Self>)
-> Result<
Box<T::__<'lt>>,
Box<Self>,
>
{
if self.is::<T>() {
let ptr: *mut () = Box::into_raw(self) as _;
// let ptr: *mut Helper<'lt, T> = ptr.cast(); // the real type
let ptr: *mut T::__<'lt> = ptr.cast(); // thanks to repr(transparent)
Ok(unsafe { Box::from_raw(ptr) })
} else {
Err(self)
}
}
}
/// `&str`
type StrRef = HKT!(&'__ str);
let local = String::from("...");
let b: Box<&str> = Box::new(&local);
let b: Box<dyn Any1<'_>> = <dyn Any1>::coerce::<StrRef>(b);
let b: Box<&str> = b.downcast::<StrRef>().unwrap_or_else(|_| unreachable!());
dbg!(b);
And to avoid needing to turbofish/clarify the T : HKT
parameter each time, I guess we could try to provide some arbitrarily/canonical injectivity in certain cases:
-
trait Is { type EqTo : ?Sized; }
impl<T : ?Sized> Is for T { type EqTo = Self; }
fn into<T>(this: T) -> <T as Is>::EqTo { this }
fn from<T>(it: <T as Is>::EqTo) -> T { it }
trait RemoveLifetime<'lt>
where
// Self = Apply<'lt, Self::HKT>
Self: Is<EqTo = Apply<'lt, Self::HKT>>,
{
type HKT : 'static + HKT;
}
impl<'lt, T : ?Sized + 'static> RemoveLifetime<'lt> for &'lt T {
type HKT = HKT!(&'__ T);
}
fn boxed_any<'lt, T : RemoveLifetime<'lt>>(value: T)
-> Box<dyn Any1<'lt>>
{
<dyn Any1<'_>>::coerce::<T::HKT>(Box::new(into(value)))
}
fn downcast<'lt, T : RemoveLifetime<'lt>>(
value: Box<dyn Any1<'lt>>,
) -> Option<T>
{
value.downcast::<T::HKT>().ok().map(|b| from(*b))
}
// Look ma, no turbofish!
let local = String::from("...");
let b: Box<dyn Any1<'_>> = boxed_any(&*local);
let b: &str = downcast(b).unwrap();
dbg!(b);
All this is not necessarily a direct answer to the OP, but I think it touches quite a few tangential things, with actual Rust code, so that questions we may have around the semantics of such may hopefully be answered or clarified through these snippets