[][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 zst type that acts like it owns T.

However PhantomData come with a number of issues

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

phantasm's naming somehow helps woth 1. making code clearer (though variance still is a hard-to-understand thing)

function pointers in const fn are unstable

It's common to make a type invariant over T with PhantomData<fn(T) -> T> however, if you've ever tryed to use it in const fn, you know that it's painfull (see rust-lang/69459 and rust-lang/67649). This crate helps mitigate this issue:

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 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 typle:

struct Test<A, B>(phantasm::Covariant<(A, B)>);

Type Definitions

Contravariant

Marker zst type that is contravariant over T.

Covariant

Marker zst type that is covariant over T.

Invariant

Marker zst type that is invariant over T.