Idea
- Allow
struct Foo<T<'a, U, const N: usize>> { ... }
,fn foo<T<...>>
, etc. - Usage:
Foo::<for<'a, U, const N: usize> Bar<'a, U, N>>
. - Unused lifetimes can be omitted:
Foo::<for<U, const N: usize> Bar<U, N>>
. - Conflict with
for<'a> fn()
: always disambiguate towards existing semantics, requirefor<'a> for<> fn
. This is because type constructors forfn
types are unlikely to be useful in practice, but nevertheless there's no reason not to support them. This is not a problem fordyn
types asdyn for<'a> Fn()
is the existing syntax andfor<'a> dyn
isn't allowed. - Interactions with inference: TBD.
- Order: TBD. See also Const generics and defaults
Motivation
Rust currently requires Any: 'static
. This is all fine and well but with generic generics it is possible to introduce a new kind of Any
hereby called AnyA<'a>: 'a
. (Note: AnyA<'static>
implies AnyA<'static>: 'static
implies Any: AnyA<'static>
- but Any
need not be a subtype of AnyA<'static>
for this proposal, nor does AnyA<'a>
need to be a part of this proposal(!)).
Its implementation, including TypeConstructorId
, is as follows:
#[repr(transparent)]
#[derive(PartialEq, Eq, ...)]
struct TypeConstructorId(TypeId);
impl TypeConstructorId {
// note: can be expanded with more lifetimes (see point 3 of "idea").
// IMPORTANT: HKTs used here (1)
fn of<T<'t_a>>() -> TypeConstructorId {
// IMPORTANT: HKTs used here (2)
TypeConstructorId(TypeId::of::<for<'a> fn(T<'a>)>())
}
}
// IMPORTANT: NO HKTs here!
trait AnyA<'a> where AnyA<'a>: 'a {
fn type_constructor_id(&self) -> TypeConstructorId;
}
// IMPORTANT: HKTs used here (3)
impl<'a, T<'t_a>> AnyA<'a> for T<'a> where T<'a>: 'a + ?Sized {
fn type_constructor_id(&self) -> TypeConstructorId {
// IMPORTANT: HKTs used here (4)
TypeConstructorId::of::<for<'for_a> T<'for_a>>()
}
}
impl<'any_a> dyn AnyA<'any_a> {
// IMPORTANT: HKTs used here (5), also inline bounds on it
pub fn is<T<'t_a>: AnyA<'t_a>>(&self) -> bool {
// IMPORTANT: HKTs used here (6)
let t = TypeConstructorId::of::<for<'for_a> T<'for_a>>();
let concrete = self.type_constructor_id();
t == concrete
}
// IMPORTANT: HKTs used here (7), also where bounds on it
pub fn downcast_ref<T<'t_a>>(&self) -> Option<&T<'any_a>>
where for<'for_a> T<'for_a>: AnyA<'for_a>
{
// IMPORTANT: HKTs used here (8)
if self.is::<for<'for_a> T<'for_a>>() {
// IMPORTANT: HKTs used here (9)
// SAFETY: AnyA<'a> doesn't operate on concrete lifetimes, instead
// it cares about how lifetimes are bound into the type and just
// carries the concrete lifetime through the type system. You cannot
// "hide" a lifetime by converting to AnyA. You must pass the lifetime
// around. With `Any`, the whole type is hidden, but with `AnyA` some
// details are (intentionally) exposed. Doesn't make it much less
// flexible tho.
unsafe { Some(&*(self as *const dyn AnyA<'any_a> as *const T<'any_a>)) }
} else {
None
}
}
}
In total, this has 9 uses of HKTs:
- Function definition that takes HKTs.
- This
T<'a>
is actually an usage of the HKTT<'t_a>
, altho thisfor<'a> fn(...)
is existing syntax. - There is a generic lifetime
'a
, as well as an HKTT<'t_a>
. Note the difference between'a
and't_a
. - Note the
for<'for_a>
as defined in this proposal. - Same as (1) but with inline bounds.
- Same as (4).
- Same as (5) but using
where
. Also note the concreteT<'any_a>
in theOption
. - Same as (4).
- Note the concrete
T<'any_a>
.
Also note that the trait AnyA<'a>
itself doesn't use HKTs, and this whole thing doesn't use any new intrinsics.
Prior discussion
See Generic generics (there may have been other stuff but we don't remember)
Benefits
AnyA<'a>
looks particularly interesting if combined with the ability to downcast between traits (i.e. implementing an OOP system on top of Rust). While this proposal isn't about adding AnyA<'a>
, nor is it about adding an OOP system on top of Rust, these make good examples/arguments for why this proposal should be implemented.
Drawbacks
There are... a lot of drawbacks to this proposal. HKTs are a monumental undertaking. There are already plenty of bugs related to constraint checking (as in where
bounds), and this would likely end up introducing so many more. Additionally, chalk still has a long way to come. Additionally additionally, who knows how this is meant to interact with inference!
Right now this proposal is mostly meant to be for defining the syntax for HKTs in Rust, as well as bringing forward an use-case that can pretty much only be satisfied with HKTs. Many languages get away with not having HKTs after all, but Rust has lifetimes, and they do get in the way: Generic Associated Types (GATs), a strict (and fairly limited) subset of HKTs, exist primarily due to lifetimes, and this use-case also boils down to lifetimes. So much so that, from talking to other Rust users, it seems likely GATs will be stabilized as lifetimes-only long before they'll support other kinds of generics. (Side note: we do, in fact, have a limited form of HKTs today: for<'a> fn()
and dyn for<'a>
. And you can only use them for... lifetimes. Yep. Sensing a pattern here. )
???
So yeah. This is a sorta HKTs proposal. Ah well. >.<