Description
EDIT by @BoxyUwU
playground
type One = for<'a> fn(&'a (), &'a ());
type Two = for<'a, 'b> fn(&'a (), &'b ());
mod my_api {
use std::any::Any;
use std::marker::PhantomData;
pub struct Foo<T: 'static> {
a: &'static dyn Any,
_p: PhantomData<*mut T>, // invariant, the type of the `dyn Any`
}
impl<T: 'static> Foo<T> {
pub fn deref(&self) -> &'static T {
match self.a.downcast_ref::<T>() {
None => unsafe { std::hint::unreachable_unchecked() },
Some(a) => a,
}
}
pub fn new(a: T) -> Foo<T> {
Foo::<T> {
a: Box::leak(Box::new(a)),
_p: PhantomData,
}
}
}
}
use my_api::*;
fn main() {
let foo = Foo::<One>::new((|_, _| ()) as One);
foo.deref();
let foo: Foo<Two> = foo;
foo.deref();
}
has UB from hitting the unreachable_unchecked
because TypeId::of::<One>()
is not the same as TypeId::of::<Two>()
despite them being considered the same types by the type checker. Originally this was thought to be a nightly-only issue with feature(generic_const_exprs)
but actually the weird behaviour of TypeId
can be seen on stable and result in crashes or UB in unsafe code.
original description follows below:
#![feature(const_type_id, generic_const_exprs)]
use std::any::TypeId;
// `One` and `Two` are currently considered equal types, as both
// `One <: Two` and `One :> Two` holds.
type One = for<'a> fn(&'a (), &'a ());
type Two = for<'a, 'b> fn(&'a (), &'b ());
trait AssocCt {
const ASSOC: usize;
}
const fn to_usize<T: 'static>() -> usize {
const WHAT_A_TYPE: TypeId = TypeId::of::<One>();
match TypeId::of::<T>() {
WHAT_A_TYPE => 0,
_ => 1000,
}
}
impl<T: 'static> AssocCt for T {
const ASSOC: usize = to_usize::<T>();
}
trait WithAssoc<U> {
type Assoc;
}
impl<T: 'static> WithAssoc<()> for T where [(); <T as AssocCt>::ASSOC]: {
type Assoc = [u8; <T as AssocCt>::ASSOC];
}
fn generic<T: 'static, U>(x: <T as WithAssoc<U>>::Assoc) -> <T as WithAssoc<U>>::Assoc
where
[(); <T as AssocCt>::ASSOC]:,
T: WithAssoc<U>,
{
x
}
fn unsound<T>(x: <One as WithAssoc<T>>::Assoc) -> <Two as WithAssoc<T>>::Assoc
where
One: WithAssoc<T>,
{
let x: <Two as WithAssoc<T>>::Assoc = generic::<One, T>(x);
x
}
fn main() {
println!("{:?}", unsound::<()>([]));
}
TypeId
being different for types which are considered equal types allows us to take change the value of a projection by switching between the equal types in its substs and observing that change by looking at their TypeId
. This is possible as switching between equal types is allowed even in invariant positions.
This means that stabilizing const TypeId::of
and allowing constants to flow into the type system, e.g. some minimal version of feature(generic_const_exprs)
, will be currently unsound.
I have no idea on how to fix this. I don't expect that we're able to convert higher ranked types to some canonical representation. Ah well, cc @rust-lang/project-const-generics @nikomatsakis