First, I need a trait for expressing higher kinded types, and one for covariant higher kinded types:
trait Hk {
type T<'a>;
}
trait Covariant: Hk
{
fn covariant<'a: 'b, 'b>(
x: Self::T<'a>,
) -> Self::T<'b>;
}
Using these traits, this function doesn't compile:
fn failing<'a, 'b, A: Covariant>(
x: &'a A::T<'b>,
) -> A::T<'a>
where
for<'p> A::T<'p>: Clone,
{
A::covariant(x.clone())
}
I can understand why the rust compiler doesn't let this compile. But I think that it would be sound to force this to compile by using unsafe. What follows is an explanation for why I think so.
An implementation of Hk
can either use or not use 'a
in T
. I will start with the case where it does not use the lifetime (I replaced the generic A
with NotContaining
in the failing
function to create the not_failing1
function):
struct NotContaining<'a>(PhantomData<*mut &'a ()>);
struct InnerNotContatining<'a>(PhantomData<*mut &'a ()>);
impl<'a> Hk for NotContaining<'a> {
// The type does not depend on 'b
type T<'b> = InnerNotContatining<'a>;
}
impl<'o> Covariant for NotContaining<'o> {
fn covariant<'a: 'b, 'b>(
x: Self::T<'a>,
) -> Self::T<'b> {
InnerNotContatining(PhantomData)
}
}
fn not_failing1<'a, 'b, 'c>(
x: &'a <NotContaining<'c> as Hk>::T<'b>,
) -> <NotContaining<'c> as Hk>::T<'a>
where
for<'p> <NotContaining<'c> as Hk>::T<'p>: Clone,
{
NotContaining::covariant(x.clone())
}
I added an extra lifetime to NotContaining
to emphasize that this should always work, even when NotContaining
is not 'static
.
Now we will look at the case where the type contains the lifetime (I replaced the generic A
with Containing
in the failing
function to create the not_failing2
function):
struct Containing;
struct InnerContaining<'a>(PhantomData<*mut &'a ()>);
impl Hk for Containing {
type T<'a> = InnerContaining<'a>;
}
impl Covariant for Containing {
fn covariant<'a: 'b, 'b>(
x: Self::T<'a>,
) -> Self::T<'b> {
InnerContaining(PhantomData)
}
}
fn not_failing2<'a, 'b>(
x: &'a <Containing as Hk>::T<'b>,
) -> <Containing as Hk>::T<'a>
where
for<'p> <Containing as Hk>::T<'p>: Clone,
{
Containing::covariant(x.clone())
}
Note that the type InnerContaining
is as restrictive as possible; it is invariant over 'a
to force the usage of Containing::covariant
.
As far as I understand, this works because the existence of the type &'a <Containing as Hk>::T<'b>
proves that 'b: 'a
, which makes it possible to cast <Containing as Hk>::T<'b>
to <Containing as Hk>::T<'a>
using Containing::covariant
. This should work for all types containing the lifetime.
In both cases described above, which I think covers all cases, the code compiles. Therefore, it should be sound to use unsafe to force it to compile:
fn not_failing_generic<'a, 'b, A: Covariant>(
x: &'a A::T<'b>,
) -> A::T<'a>
where
for<'p> A::T<'p>: Clone,
{
unsafe{ core::mem::transmute(A::covariant(x.clone()))}
}
I am not at all certain that my logic is right. Therefore, I would like if someone confirms this logic or give an example where this function can be used to invoke undefined behavior. I am sorry if this has been discussed earlier, but it is so specific and obscure that I do not think I could find it in that case.
In case anyone wonders, I don't really have a good use case for this. This is mostly hypothetical. I have a little more real use case, but unless someone actually cares, I will not take the time to write it down.