You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Given that the variance of generic types is purely implicit, it would be greatly beneficial to have assertions to test the expected variance of a struct.
this is my proposed syntax:
assert_is_covariant!(for['a,T](Foo<'a,'b,T>) over 'b);assert_is_contravariant!((fn(i32,T) -> bool) over T);
Which means "assert that for all lifetimes 'a and types T, Foo<'a, 'b, T> is covariant over 'b". Unfortunately, given the restrictions of declarative macros currently, it would be very challenging to make a for<...> syntax instead of for[...]. Additionally, if the for clause is not needed, it could just be omitted entirely. Note that the lifetime being tested cannot be included in the for clause.
This is how it would perform the test:
Assume that 'a is the lifetime being tested, Type is the fully qualified type (like &'a U), and GenericParams is everything in the for[...] clause.
Create a local type, e.g., Cov, parameterized with the lifetime being tested, followed by the remaining generic parameters, and wrapping the provided type, i.e., struct Cov<'a, GenericParams...>(Type); Since 'a is referred to within Type, by ensuring that 'a is the first parameter of Cov, it allows us to refer to refer to Type but with our own lifetimes replacing 'a.
Create a function, e.g., test_cov taking generic parameters <'__a: '__b, '__b, GenericParams...> and value parameters sub: *const Cov<'__a, GenericParams...> and mut sup: *const Cov<'__b, GenericParams...>.
Then in the body, assert that the type of sub is indeed a subtype of sup by performing the assignment sup = sub;.
The implementation of assert_is_contravariant is almost identical, but would say sub = sup; instead, with corresponding changes to mutability.
The reason *const is used around the sup/sub parameters is to ensure that this test can be performed even if T: !Sized.
In the case of testing a type parameter T being tested, it would do the following:
Define a local typedef, e.g., type Transform<GenericParams..., T> = Type;
Recurse, invoking:
assert_is_covariant!(for[GenericParams...](Transform<GenericParams...,&'__a ()>) over '__a);
This works because &'a () is always covariant over 'a. As a result, the subtyping
relation between any &'x () and &'y () always matches the relation between lifetimes 'x
and 'y. Therefore, testing variance over a type parameter T can be replaced by testing
variance over lifetime 'a in &'a ().
Even though this only checks cases where T is a reference, since a type constructor can be
ONLY covariant, contravariant, or invariant over a type parameter, if it is works in this case
it proves that the type is covariant in all cases.
The main downside to this approach is if there is some trait bound on T which &() does not implement. In this case, the user must simply provide a type known to satisfy the trait bounds that is covariant over some lifetime parameter, and then test variance over that lifetime.
The text was updated successfully, but these errors were encountered:
Given that the variance of generic types is purely implicit, it would be greatly beneficial to have assertions to test the expected variance of a struct.
this is my proposed syntax:
Which means "assert that for all lifetimes
'a
and typesT
,Foo<'a, 'b, T>
is covariant over'b
". Unfortunately, given the restrictions of declarative macros currently, it would be very challenging to make afor<...>
syntax instead offor[...]
. Additionally, if thefor
clause is not needed, it could just be omitted entirely. Note that the lifetime being tested cannot be included in thefor
clause.This is how it would perform the test:
Assume that
'a
is the lifetime being tested,Type
is the fully qualified type (like&'a U
), andGenericParams
is everything in thefor[...]
clause.Create a local type, e.g.,
Cov
, parameterized with the lifetime being tested, followed by the remaining generic parameters, and wrapping the provided type, i.e.,struct Cov<'a, GenericParams...>(Type);
Since'a
is referred to withinType
, by ensuring that'a
is the first parameter ofCov
, it allows us to refer to refer toType
but with our own lifetimes replacing'a
.Create a function, e.g.,
test_cov
taking generic parameters<'__a: '__b, '__b, GenericParams...>
and value parameterssub: *const Cov<'__a, GenericParams...>
andmut sup: *const Cov<'__b, GenericParams...>
.Then in the body, assert that the type of
sub
is indeed a subtype ofsup
by performing the assignmentsup = sub;
.The implementation of
assert_is_contravariant
is almost identical, but would saysub = sup;
instead, with corresponding changes to mutability.The reason
*const
is used around thesup/sub
parameters is to ensure that this test can be performed even ifT: !Sized
.In the case of testing a type parameter
T
being tested, it would do the following:type Transform<GenericParams..., T> = Type;
This works because
&'a ()
is always covariant over'a
. As a result, the subtypingrelation between any
&'x ()
and&'y ()
always matches the relation between lifetimes'x
and
'y
. Therefore, testing variance over a type parameterT
can be replaced by testingvariance over lifetime
'a
in&'a ()
.Even though this only checks cases where T is a reference, since a type constructor can be
ONLY covariant, contravariant, or invariant over a type parameter, if it is works in this case
it proves that the type is covariant in all cases.
The main downside to this approach is if there is some trait bound on
T
which&()
does not implement. In this case, the user must simply provide a type known to satisfy the trait bounds that is covariant over some lifetime parameter, and then test variance over that lifetime.The text was updated successfully, but these errors were encountered: