Expand description
This crate is aiming to make work with variance easier.
The crate exposes 3 types - Invariant<T>, Covariant<T> and
Contravariant<T> with corresponding variance over T those work
in a very similar way to PhantomData<_>.
§motivation
In rust it’s an error to have an unused generic param in struct:
struct Slice<'a, T> {
start: *const T,
end: *const T,
}error[E0392]: parameter `'a` is never used
--> src/lib.rs:16:14
|
3 | struct Slice<'a, T> {
| ^^ unused parameter
|
= help: consider removing `'a`, referring to it in a field, or using a marker such as `std::marker::PhantomData`This is an error because rust compiler doesn’t know if Slice should be
covariant, contravariant or invariant over 'a. What this means is that
rustc doesn’t know if Slice<'static, _> should be a subtype of Slice<'a, _> or vice versa or neither. See Subtyping and Variance nomicon
chapter for better explanation.
To mitigate this issue and control the variance there is a type called
marker::PhantomData<T>. PhantomData<T> is a
zero-sized type that acts like it owns T.
However, PhantomData comes with a number of issues:
- Variance is a hard thing to understand by itself, but
PhantomDatamakes it even harder to understand. It’s not straightforward to understand what statement likePhantomData<fn(A, B) -> B>does (contravariant overAand invariant overB) - Sometimes it works badly in
constcontext (see next paragraph)
phantasm’s naming helps with the first issue by making the original
intention clearer (though variance still is a hard-to-understand thing) and
with the second by doing some hacks under the hood.
§function pointers in const fn are unstable
It’s common practice to make a type invariant over T with
PhantomData<fn(T) -> T>. However, if you’ve ever tried to use it in a
const fn, you know that it’s painful (see rust-lang/69459 and
rust-lang/67649) because before Rust 1.61.0 function pointers
in const fn were unstable, see stabilization PR for more. This crate
helps with this problem:
use phantasm::Invariant;
pub struct Test<T>(Invariant<T>);
impl<T> Test<T> {
pub const fn new() -> Self {
Self(Invariant) // just works (even on old rust)
}
}§lifetimes
For variance over lifetimes, use Lt<'l>:
use phantasm::{Contravariant, Covariant, Invariant, Lt};
struct Test<'a, 'b, 'c>(Invariant<Lt<'a>>, Covariant<Lt<'b>>, Contravariant<Lt<'c>>);§comparison operators cannot be chained
Note: you can’t use Invariant<Ty> as a value (just as
PhantomData). To create Invariant<Ty> value use turbofish:
Invariant::<Ty> (same goes for both Covariant<T> and
Contravariant<T>)
// won't compile
let _ = phantasm::Invariant<i32>;use phantasm::Invariant;
// ok
let _ = Invariant::<i32>;
// Both forms are acceptable in type position
struct NoFish<T>(Invariant<T>);
struct Turbofish<T>(Invariant<T>);§many types
When you need to set variance of many types at once, just use a tuple:
struct Test<A, B>(phantasm::Covariant<(A, B)>);§MSRV
Minimal supported rustc version is 1.40.0.
I don’t expect this crate to be changed much,
so MSRV will likely stay constant for the rest of eternity.
Re-exports§
pub use super::Contravariant::Contravariant;pub use super::Covariant::Covariant;pub use super::Invariant::Invariant;pub use super::Lt::Lt;
Type Aliases§
- Contravariant
- Marker zero-sized type that is contravariant over
T. - Covariant
- Marker zero-sized type that is covariant over
T. - Invariant
- Marker zero-sized type that is invariant over
T. - Lt
- Marker zero-sized type that is covariant over
'lifetime.