#no-std #phantom #unused

no-std phantasm

Small lib that helps with variance

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

#525 in Rust patterns

MIT license

21KB
145 lines

phantasm

CI status crates.io documentation (docs.rs) documentation (master) LICENSE

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:

  1. 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 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 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};

# // yep, I just don't want to copy&paste everything yet again
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.

No runtime deps