(I know I deleted a post, I just didn't finished writing it and I just realized too late I could just edit)
Guess what, I think I a bit too stubborn because I kept looking for a sound solution. Once again, I'm posting this here because I want people to break it.
Tell me if I'm wrong, but given two Sized
types T
and U
, having T: U
⇒ T
can be safely transmuted into U
. For example, u32: u32
so it's safe to transmute u32
into u32
. This is pretty obvious, but let's do it with references. 'a: 'b
⇒ &'a u32: &'b u32
⇒ &'a u32
can be safely transmuted into &'b u32
.
This also work for contravariant and invariant lifetimes. 'T: U
⇒ fn(U): fn(T)
⇒ fn(U)
can be safely transmuted into fn(T)
. Also, Cell<T>: Cell<T>
because Cell<T>
is invariant in T
.
If this is correct, then it should be possible to write somthing like this.
// Safety:
//
// `Self` must be a subtype of `T`.
unsafe trait SubtypeOf<T: ?Sized> {}
This trait can be implemented for any T
that doesn't have lifetime paramters.
unsafe impl SubtypeOf<u32> for u32 {}
And the reasoning I did above can be used here. Rust doesn't have a T: U
syntax but this is basically
the same thing.
// Safety:
//
// `&'a T` is covariant in `'a` so we have to require `'a: 'b`.
// `&'a T` is covariant in `T` so we have to require `T: U`.
unsafe impl<'a, 'b, U, T> SubtypeOf<&'b U> for &'a T
where
'a: 'b,
U: ?Sized,
T: ?Sized + SubtypeOf<U>,
{}
// Safety:
//
// `fn(T)` is contravariant in `T` so we have to require `U: T`.
unsafe impl<U, T> SubtypeOf<fn(U)> for fn(T)
where
U: SubtypeOf<T>,
{}
// Safety:
//
// This implementation requires `T` to be the same.
unsafe impl<T: ?Sized> SubtypeOf<Cell<T>> for Cell<T> {}
This pretty basic and not really useful. But this does mean that we can write a safe version of transmute.
/// Safely transmutes a `T` into an `U`.
fn safe_transmute<T, U>(value: T) -> U
where
T: SubtypeOf<U>
{
let value = std::mem::ManuallyDrop::new(value);
unsafe { (&*value as *const T as *const U).read() }
}
Our problem is that TypeId
equality completely forgets about lifetimes. But if we could somehow remember the type subtyping relationship of T
and U
regarding their lifetime parameters, it would be possible to write a fallible version of safe_transmute
.
Introducing: my new attempt.
/// # Safety
///
/// Let `F` be the type constructor of `Self`.
///
/// * `'cov` must be the lifetime of all covariant lifetime paramters of `F`.
/// * `'con` must be the lifetime of all contravariant lifetime parameters of `F`.
/// * `'inv` must be the lifetime of all invariant lifetime parameters of `F`.
pub unsafe trait Any<'cov, 'con, 'inv> {
fn type_id(&self) -> u64 { type_id::<Self>() }
}
From this, we can create our new safe_transmute
. Let's call it checked_transmute
.
/// Safely transmutes a `T` into an `U`.
fn checked_transmute<'cov_t, 'con_t, 'cov_u, 'con_u, 'inv, T, U>(value: T) -> Result<U, T>
where
T: Any<'cov_t, 'con_t, 'inv>,
U: Any<'cov_u, 'con_u, 'inv>,
'cov_t: 'cov_u,
'con_u: 'con_t,
{
if type_id::<T>() == type_id::<U>() {
let value = std::mem::ManuallyDrop::new(value);
// Safety:
//
// We know that T and U are equal in their IDs, meaning they can only
// differ in their lifetimes.
//
// The signature of the function ensures that `U: T` as long as they are the only
// only thing that differs is the lifetimes.
//
// This should be safe?
Ok(unsafe { std::ptr::read(&*value as *const T as *const U) })
} else {
Err(value)
}
}
The idea is that TypeId
equality only check for half the requirements of a safe transmutation. Keeping the other half in Any
's trait constructor (is that a thing ? A trait constructor ? You got it.)
If this function really is safe (and it probably isn't), then writing downcast_ref
and downcast_mut
is trivial.
This new Any
trait is basically a generalization of the old OutlivedBy<'a>
which only took covariant lifetimes in account.
Deriving Any
isn't very difficult, but it's not possible to write a blanket implementation for any T
with Rust's current syntax. Here is some examples (removed the unsafe impl
for clarity).
// Safety:
//
// `str` doesn have lifetime paramters. Any lifetime works for this type.
impl<'cov, 'con, 'inv> Any<'cov, 'con, 'inv> for str {}
// Safety:
//
// `&'a T` is covariant in `'a`, so we have to put `'cov` in its place.
// `&'a T` is covariant in `T`, so we simply have to forward the lifetimes.
impl<'cov, 'con, 'inv, T> Any<'cov, 'con, 'inv> for &'cov T
where
T: Any<'cov, 'con, 'inv>,
{}
// Safety:
//
// `Cell<T>` is invariant in `T`, so all the lifetimes of `T` have to be frozen.
impl<'cov, 'con, 'inv, T> Any<'cov, 'con, 'inv> for Cell<T>
where
T: Any<'inv, 'inv, 'inv>,
{}
// Safety:
//
// `fn(T)` is contravariant in `U`, so the lifetimes have to be forwarded to `T` by
// swapping the `'cov` and `'con` lifetimes.
impl<'cov, 'con, 'inv, T> Any<'cov, 'con, 'inv> for fn(T)
where
T: Any<'con, 'cov, 'inv>,
{}
Try it on the playground!