4 releases
0.1.3 | Apr 23, 2022 |
---|---|
0.1.2 | Oct 11, 2020 |
0.1.1 | May 14, 2020 |
0.1.0 | May 14, 2020 |
#2777 in Rust patterns
21KB
145 lines
phantasm
phantasm
provides tools for working with variance. See docs for more.
[dependencies]
phantasm = "0.1"
Compiler support: requires rustc 1.40+
lib.rs
:
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
PhantomData
makes it even harder to understand. It's not straightforward to understand what statement likePhantomData<fn(A, B) -> B>
does (contravariant overA
and invariant overB
) - Sometimes it works badly in
const
context (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.