[][src]Crate phantasm

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 very similar to PhantomData<_>.

motivation

In rust it's an error to have an unused generic param in struct:

This example deliberately fails to compile
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 should Slice be covariant, contravariant or invariant over 'a. (What it means is that rustc doesn't know if Slice<'static, _> should be Slice<'a, _> or vice versa or none of this. See Subtyping and Variance nomicon chapter for better explanation)

To mitigate this issue and control the variance there is a type called core::marker::PhantomData<T>. PhantomData<T> is a zero-sized type that acts like it owns T.

However, PhantomData comes with a number of issues:

  1. Variance is a hard thing by itself, but PhantomData makes it even harder to understand. It's not straightforward to understand what statement like PhantomData<fn(A, B) -> B> does (contravariant over A and invariant over B)
  2. Sometimes it works badly in const context (see next paragraph)

phantasm's naming helps with the first by making the original intention clearer (though variance still is a hard-to-understand thing)

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). 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
    }
}

life

For variance over lifetimes, use &'l ():

use phantasm::{Contravariant, Covariant, Invariant};

struct Test<'a, 'b, 'c>(Invariant<&'a ()>, Covariant<&'b ()>, Contravariant<&'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>)

This example deliberately fails to compile
// won't compile
let _ = phantasm::Invariant<i32>;
use phantasm::Invariant;
// ok
let _ = Invariant::<i32>;
// Both forms are acceptable in type possition
struct A<T>(Invariant<T>);
struct B<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)>);

Type Definitions

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.