Module ecstatic::typelist [−][src]
Expand description
Type-level metaprogramming traits that allow the library to validate certain invariants at compile time.
This is broadly adapted from Lloyd Chan’s excellent article Gentle Intro to Type-level Recursion in Rust. However, since it’s only used to enforce invariants, the resulting heterogeneous list type doesn’t contain any actual storage; this means that, at least in theory, the compiler should be able to optimize it out completely.
The meat of the module is found in the TypeList
,
Consume
, and ConsumeMultiple
traits.
TypeList
s are cons
-style singly linked lists expressed in the type system;
the trait is implemented by TypeCons
and Nil
.
As mentioned above, the types are effectively empty, and since Nil
is an empty enum, cannot
even be constructed.
The way this is used in the library is to indicate what types are available for Systems to access within a World.
Examples
TypeList
s are constructed from lisp-style cons
cells, terminating with Nil
.
type AvailableTypes = TypeCons<f64, TypeCons<u32, TypeCons<String, Nil>>>;
The tlist!
macro is provided to make writing these types easier and
less verbose.
type AvailableTypes = tlist![f64, u32, String];
In this example, do_stuff()
will take an argument of type f64
, u32
, or String
. I
is a
type parameter used by Consume
; it should be left up to the type checker to infer. It’s kind
of a bummer that this has to leak into the public interface, but that’s the way it is.
type AvailableTypes = tlist![f64, u32, String]; fn do_stuff<T, I>(t: T) where AvailableTypes: Consume<T, I> { // Do something with `t` } do_stuff(25.0f64); do_stuff(42u32); do_stuff(String::from("Hello!"));
Calling do_struff()
with types that are not in AvailableTypes
will fail to type check.
struct Whatever { x: f32, y: f32, } type AvailableTypes = tlist![f64, u32, String]; fn do_stuff<T, I>(t: T) where AvailableTypes: Consume<T, I> { // Do something with `t` } do_stuff(Whatever { x: 1.0, y: 3.0 });
Unfortunately, the error messages you get from the type checker failing are not particularly helpful. For instance, in the example above, you will get something like the following:
error[E0277]: the trait bound `main::ecstatic::typelist::Nil: main::ecstatic::typelist::Consume<main::Whatever, _>` is not satisfied
--> src/lib.rs:75:1
|
14 | do_stuff(Whatever { 1.0, 3.0 });
| ^^^^^^^^ the trait `main::ecstatic::typelist::Consume<main::Whatever, _>` is not implemented for `main::ecstatic::typelist::Nil`
|
Not the greatest indicator of what the actual problem is.
There is also a trait, ConsumeMultiple
, that takes a TypeList
as its type parameter (along
with a similar “Index” type that you should let the compiler infer, like with Consume
).
type AvailableTypes = tlist![f64, u32, String]; fn do_stuff<T, I>() where AvailableTypes: ConsumeMultiple<T, I> { // Do something } do_stuff::<tlist![f64, u32], _>();
This similarly will fail to type check if not all of the types are available in the source list.
type AvailableTypes = tlist![f64, u32, String]; fn do_stuff<T, I>() where AvailableTypes: ConsumeMultiple<T, I> { // Do something } do_stuff::<tlist![f64, u32, &str], _>();
Importantly, Consume<T, I>
removes all instances of T
from the source list; this allows
us to write generic functions over T
, U
such that T != U
(!).
fn do_stuff<T, U, I>() where tlist![T, U]: ConsumeMultiple<tlist![T, U], I> { // Do something } do_stuff::<u32, f64, _>();
fn do_stuff<T, U, I>() where tlist![T, U]: ConsumeMultiple<tlist![T, U], I> { // Do something } // Using the same type for `T` and `U` causes a compilation error along the lines of the // following: // // error[E0282]: type annotations needed // --> src/ecs.rs:147:1 // | // 8 | do_stuff::<u32, u32, _>(); // | ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for `IHEAD` do_stuff::<u32, u32, _>();
There is also a trait called IntoTypeList
that allows easy conversion from tuples (up to
length 32) to TypeList
.
type AvailableTypes = tlist![f64, u32, String]; fn do_stuff<T, U, I>() where T: IntoTypeList<Type=U>, // For some reason we still need to put a trait bound on `U`, even though the associated // type is constrained in `IntoTypeList` U: TypeList, AvailableTypes: ConsumeMultiple<U, I> { // Do something } do_stuff::<(String, f64), _, _>();
Structs
NotFound | Index struct for |
TypeCons | A cons cell |
Enums
Found | Index for |
Nil | The empty list. |
Traits
Append | Generically append |
Consume | Removes all instances of |
ConsumeMultiple | Remove multiple elements, leaving |
IntoTypeList | Easy conversion into |
TypeList | Trait implemented by |