pub struct TypeEq<L, R>(/* private fields */)
where
L: ?Sized,
R: ?Sized;
Expand description
Value-level proof that L
is the same type as R
This type can be used to prove that L
and R
are the same type,
because it can only be safely constructed with
TypeEq::<L, L>::NEW
(or new
),
where both type arguments are the same type.
This type is not too useful by itself, it becomes useful when put inside of an enum.
TypeEq<L, R>
uses the L
type parameter as the more generic type by convention
(e.g: TypeEq<T, char>
).
This only matters if you’re using the type witness traits
(HasTypeWitness
,
MakeTypeWitness
,
TypeWitnessTypeArg
) with TypeEq
.
§Soundness
TypeEq<L, R>
requires both type arguments to be the same type so that
projecting the type arguments results in the same type for
both arguments.
Unsafely creating a TypeEq<L, R>
where L != R
allows
transmuting between any two types
(that is bad).
§Examples
§Polymorphic branching
This example demonstrates how type witnesses can be used to choose between expressions of different types with a constant.
use typewit::TypeEq;
const fn main() {
assert!(matches!(choose!(0; b"a string", 2, panic!()), b"a string"));
const UNO: u64 = 1;
assert!(matches!(choose!(UNO; loop{}, [3, 5], true), [3, 5]));
assert!(matches!(choose!(2 + 3; (), unreachable!(), ['5', '3']), ['5', '3']));
}
/// Evaluates the argument at position `$chosen % 3`, other arguments aren't evaluated.
///
/// The arguments can all be different types.
///
/// `$chosen` must be a `u64` constant.
#[macro_export]
macro_rules! choose {
($chosen:expr; $arg_0: expr, $arg_1: expr, $arg_2: expr) => {
match Choice::<{$chosen % 3}>::VAL {
// `te` (a `TypeEq<T, X>`) allows us to safely go between
// the type that the match returns (its `T` type argument)
// and the type of `$arg_0` (its `X` type argument).
Branch3::A(te) => {
// `to_left` goes from `X` to `T`
te.to_left($arg_0)
}
// same as the `A` branch, with a different type for the argument
Branch3::B(te) => te.to_left($arg_1),
// same as the `A` branch, with a different type for the argument
Branch3::C(te) => te.to_left($arg_2),
}
}
}
// This is a type witness
pub enum Branch3<T, X, Y, Z> {
// This variant requires `T == X`
A(TypeEq<T, X>),
// This variant requires `T == Y`
B(TypeEq<T, Y>),
// This variant requires `T == Z`
C(TypeEq<T, Z>),
}
// Used to get different values of `Branch3` depending on `N`
pub trait Choice<const N: u64> {
const VAL: Self;
}
impl<X, Y, Z> Choice<0> for Branch3<X, X, Y, Z> {
// Because the first two type arguments of `Branch3` are `X`
// (as required by the `TypeEq<T, X>` field in Branch3's type definition),
// we can use `TypeEq::NEW` here.
const VAL: Self = Self::A(TypeEq::NEW);
}
impl<X, Y, Z> Choice<1> for Branch3<Y, X, Y, Z> {
const VAL: Self = Self::B(TypeEq::NEW);
}
impl<X, Y, Z> Choice<2> for Branch3<Z, X, Y, Z> {
const VAL: Self = Self::C(TypeEq::NEW);
}
Implementations§
Source§impl<T> TypeEq<T, T>where
T: ?Sized,
impl<T> TypeEq<T, T>where
T: ?Sized,
Sourcepub const NEW: TypeEq<T, T> = _
pub const NEW: TypeEq<T, T> = _
Constructs a TypeEq<T, T>
.
§Example
use typewit::TypeEq;
assert_eq!(mutate(5, Wit::U32(TypeEq::NEW)), 25);
assert_eq!(mutate(5, Wit::Other(TypeEq::NEW)), 5);
assert_eq!(mutate("hello", Wit::Other(TypeEq::NEW)), "hello");
const fn mutate<W>(val: W, wit: Wit<W>) -> W {
match wit {
Wit::U32(te) => te.to_left(te.to_right(val) + 20),
Wit::Other(_) => val,
}
}
// This can't be written using the `simple_type_witness` macro because the
// type in the `Other` variant overlaps with the other ones.
enum Wit<W> {
U32(TypeEq<W, u32>),
Other(TypeEq<W, W>),
}
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub fn with_any() -> Option<TypeEq<L, R>>
pub fn with_any() -> Option<TypeEq<L, R>>
Constructs TypeEq<L, R>
if L == R
, otherwise returns None.
§Example
use typewit::TypeEq;
use std::any::Any;
assert_eq!(sum_u32s(&[3u32, 5, 8]), Some(16));
assert_eq!(sum_u32s(&[3i32, 5, 8]), None);
fn sum_u32s<T: Clone + Any>(foo: &[T]) -> Option<u32> {
typecast_slice::<T, u32>(foo)
.map(|foo: &[u32]| foo.iter().copied().sum())
}
fn typecast_slice<T: Any, U: Any>(foo: &[T]) -> Option<&[U]> {
struct SliceFn;
impl<T> typewit::TypeFn<T> for SliceFn {
type Output = [T];
}
TypeEq::<T, U>::with_any().map(|te: TypeEq<T, U>|{
te.map(SliceFn) // TypeEq<[T], [U]>
.in_ref() // TypeEq<&[T]>, &[U]>
.to_right(foo) // identity cast from `&[T]` to `&[U]`
})
}
Sourcepub const unsafe fn new_unchecked() -> TypeEq<L, R>
pub const unsafe fn new_unchecked() -> TypeEq<L, R>
Constructs a TypeEq<L, R>
.
§Safety
You must ensure that L
is the same type as R
.
§Examples
§Unsound usage
This example demonstrates why L == R
is a strict requirement.
use typewit::{TypeEq, TypeFn};
// SAFETY: WRONG! UNSOUND!
let te: TypeEq<u8, i8> = unsafe{ TypeEq::new_unchecked() };
// Because `TypeEq<u8, i8>` is incorrect,
// we get this absurd `TypeEq` from the `project` method.
let absurd: TypeEq<(), Vec<usize>> = te.project::<Func>();
// This casts from `()` to `Vec<usize>` (which is UB).
// Last time I tried uncommenting this, it killed the test runner.
// absurd.to_right(());
struct Func;
impl TypeFn<u8> for Func { type Output = (); }
impl TypeFn<i8> for Func { type Output = Vec<usize>; }
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub const fn flip(self) -> TypeEq<R, L>
pub const fn flip(self) -> TypeEq<R, L>
Swaps the type parameters of this TypeEq
§Example
use typewit::TypeEq;
assert_eq!(flip_bytes([3, 5], TypeEq::NEW), [5, 3]);
const fn flip_bytes<T>(val: T, te: TypeEq<T, [u8; 2]>) -> T {
bar(val, te.flip())
}
const fn bar<T>(val: T, te: TypeEq<[u8; 2], T>) -> T {
let [l, r] = te.to_left(val);
te.to_right([r, l])
}
Sourcepub const fn join<O>(self, _other: TypeEq<R, O>) -> TypeEq<L, O>where
O: ?Sized,
pub const fn join<O>(self, _other: TypeEq<R, O>) -> TypeEq<L, O>where
O: ?Sized,
Joins this TypeEq<L, R>
with a TypeEq<R, O>
, producing a TypeEq<L, O>
.
The returned TypeEq
can then be used to coerce between L
and O
.
§Example
use typewit::TypeEq;
assert_eq!(foo(TypeEq::NEW, TypeEq::NEW, Some(3)), Some(3));
assert_eq!(foo(TypeEq::NEW, TypeEq::NEW, None), None);
fn foo<L, X>(
this: TypeEq<L, Option<X>>,
that: TypeEq<Option<X>, Option<u32>>,
value: Option<u32>,
) -> L {
let te: TypeEq<L, Option<u32>> = this.join(that);
te.to_left(value)
}
Source§impl<L0, R0> TypeEq<L0, R0>
impl<L0, R0> TypeEq<L0, R0>
Sourcepub const fn zip<L1, R1>(
self,
other: TypeEq<L1, R1>,
) -> TypeEq<(L0, L1), (R0, R1)>
pub const fn zip<L1, R1>( self, other: TypeEq<L1, R1>, ) -> TypeEq<(L0, L1), (R0, R1)>
Combines this TypeEq<L0, R0>
with a TypeEq<L1, R1>
,
producing a TypeEq<(L0, L1), (R0, R1)>
.
§Alternative
For an alternative which allows zipping TypeEq
with any
BaseTypeWitness
,
you can use methods::zip2
(requires the "rust_1_65"
feature)
§Example
This example demonstrates how one can combine two TypeEq
s to use
with a multi-parameter type.
use typewit::{const_marker::Usize, TypeEq, TypeFn};
assert_eq!(make_foo(TypeEq::NEW, TypeEq::NEW), Foo("hello", [3, 5, 8]));
const fn make_foo<T, const N: usize>(
te_ty: TypeEq<T, &'static str>,
te_len: TypeEq<Usize<N>, Usize<3>>,
) -> Foo<T, N> {
// the type annotations are just for the reader, they can be inferred.
let te_pair: TypeEq<(T, Usize<N>), (&str, Usize<3>)> = te_ty.zip(te_len);
let te: TypeEq<Foo<T, N>, Foo<&str, 3>> = te_pair.project::<GFoo>();
// `te.to_left(...)` here goes from `Foo<&str, 3>` to `Foo<T, N>`
te.to_left(Foo("hello", [3, 5, 8]))
}
#[derive(Debug, PartialEq)]
struct Foo<T, const N: usize>(T, [u8; N]);
typewit::type_fn!{
// Type-level function from `(T, Usize<N>)` to `Foo<T, N>`
struct GFoo;
impl<T, const N: usize> (T, Usize<N>) => Foo<T, N>
}
Sourcepub const fn zip3<L1, R1, L2, R2>(
self,
other1: TypeEq<L1, R1>,
other2: TypeEq<L2, R2>,
) -> TypeEq<(L0, L1, L2), (R0, R1, R2)>
pub const fn zip3<L1, R1, L2, R2>( self, other1: TypeEq<L1, R1>, other2: TypeEq<L2, R2>, ) -> TypeEq<(L0, L1, L2), (R0, R1, R2)>
Combines three TypeEq<L*, R*>
to produce a
TypeEq<(L0, L1, L2), (R0, R1, R2)>
.
§Alternative
For an alternative which allows zipping TypeEq
with two of any
BaseTypeWitness
,
you can use methods::zip3
(requires the "rust_1_65"
feature)
§Example
use typewit::{TypeEq, type_eq};
use std::cmp::Ordering::{self, Less};
assert_eq!(make_tuple(type_eq(), type_eq(), type_eq()), (3, "foo", Less));
fn make_tuple<A, B, C>(
te0: TypeEq<A, u8>,
te1: TypeEq<B, &str>,
te2: TypeEq<C, Ordering>,
) -> (A, B, C) {
te0.zip3(te1, te2) // returns `TypeEq<(A, B, C), (u8, &str, Ordering)>`
.to_left((3, "foo", Less))
}
Sourcepub const fn zip4<L1, R1, L2, R2, L3, R3>(
self,
other1: TypeEq<L1, R1>,
other2: TypeEq<L2, R2>,
other3: TypeEq<L3, R3>,
) -> TypeEq<(L0, L1, L2, L3), (R0, R1, R2, R3)>
pub const fn zip4<L1, R1, L2, R2, L3, R3>( self, other1: TypeEq<L1, R1>, other2: TypeEq<L2, R2>, other3: TypeEq<L3, R3>, ) -> TypeEq<(L0, L1, L2, L3), (R0, R1, R2, R3)>
Combines four TypeEq<L*, R*>
to produce a
TypeEq<(L0, L1, L2, L3), (R0, R1, R2, L3)>
.
§Alternative
For an alternative which allows zipping TypeEq
with three of any
BaseTypeWitness
,
you can use methods::zip4
(requires the "rust_1_65"
feature)
§Example
use typewit::{TypeEq, type_eq};
use std::cmp::Ordering::{self, Less};
assert_eq!(
make_tuple(type_eq(), type_eq(), type_eq(), type_eq()),
(3, "foo", Less, true),
);
fn make_tuple<A, B, C, D>(
te0: TypeEq<A, u8>,
te1: TypeEq<B, &str>,
te2: TypeEq<C, Ordering>,
te3: TypeEq<D, bool>,
) -> (A, B, C, D) {
let te: TypeEq<(A, B, C, D), (u8, &str, Ordering, bool)> = te0.zip4(te1, te2, te3);
te.to_left((3, "foo", Less, true))
}
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub const fn reachability_hint<T>(self, val: T) -> T
pub const fn reachability_hint<T>(self, val: T) -> T
Hints to the compiler that a TypeEq<L, R>
can only be constructed if L == R
.
This function takes and returns val
unmodified.
This allows returning some value from an expression
while hinting that L == R
.
Sourcepub const fn to_right(self, from: L) -> R
pub const fn to_right(self, from: L) -> R
A no-op cast from L
to R
.
This cast is a no-op because having a TypeEq<L, R>
value
proves that L
and R
are the same type.
§Example
use typewit::{TypeEq, type_eq};
use std::cmp::Ordering::{self, *};
assert_eq!(mutated(Less, Wit::Ord(type_eq())), Greater);
assert_eq!(mutated(Equal, Wit::Ord(type_eq())), Equal);
assert_eq!(mutated(Greater, Wit::Ord(type_eq())), Less);
assert_eq!(mutated(false, Wit::Bool(type_eq())), true);
assert_eq!(mutated(true, Wit::Bool(type_eq())), false);
const fn mutated<R>(arg: R, w: Wit<R>) -> R {
match w {
Wit::Ord(te) => te.to_left(te.to_right(arg).reverse()),
Wit::Bool(te) => te.to_left(!te.to_right(arg)),
}
}
enum Wit<R> {
Ord(TypeEq<R, Ordering>),
Bool(TypeEq<R, bool>),
}
Sourcepub const fn to_left(self, from: R) -> L
pub const fn to_left(self, from: R) -> L
A no-op cast from R
to L
.
This cast is a no-op because having a TypeEq<L, R>
value
proves that L
and R
are the same type.
§Example
use typewit::{TypeEq, type_eq};
assert_eq!(stuff(Wit::OptSlice(type_eq())), Some(&[3, 5, 8][..]));
assert_eq!(stuff(Wit::Bool(type_eq())), true);
const fn stuff<R>(te: Wit<R>) -> R {
match te {
Wit::OptSlice(te) => te.to_left(Some(&[3, 5, 8])),
Wit::Bool(te) => te.to_left(true),
}
}
enum Wit<R> {
OptSlice(TypeEq<R, Option<&'static [u16]>>),
Bool(TypeEq<R, bool>),
}
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub const fn map<F>(
self,
func: F,
) -> TypeEq<<F as TypeFn<L>>::Output, <F as TypeFn<R>>::Output>
pub const fn map<F>( self, func: F, ) -> TypeEq<<F as TypeFn<L>>::Output, <F as TypeFn<R>>::Output>
Maps the type arguments of this TypeEq
by using the F
type-level function.
Use this function over project
if you want the type of the passed in function to be inferred.
§Example
use typewit::{TypeEq, TypeFn};
assert_eq!(foo(TypeEq::NEW), (false, 5));
const fn foo<'a, T>(te: TypeEq<u32, T>) -> (bool, T) {
// `GPair<bool>` maps `u32` to `(bool, u32)`
// and maps `T` to `(bool, T)`
let map_te: TypeEq<(bool, u32), (bool, T)> = te.map(GPair::<bool>::NEW);
// same as the above, but inferring `GPair`'s generic arguments.
let _: TypeEq<(bool, u32), (bool, T)> = te.map(GPair::NEW);
map_te.to_right((false, 5u32))
}
// Declares `struct GPair<A>`, a type-level function from `B` to `(A, B)`
typewit::type_fn! {
struct GPair<A>;
impl<B> B => (A, B)
}
Sourcepub const fn project<F>(
self,
) -> TypeEq<<F as TypeFn<L>>::Output, <F as TypeFn<R>>::Output>
pub const fn project<F>( self, ) -> TypeEq<<F as TypeFn<L>>::Output, <F as TypeFn<R>>::Output>
Maps the type arguments of this TypeEq
by using the F
type-level function.
Use this function over map
if you want to specify the type of the passed in function explicitly.
§Example
use typewit::{TypeEq, TypeFn};
assert_eq!(foo(TypeEq::NEW), vec![3u32, 5, 8]);
fn foo<T>(te: TypeEq<u32, T>) -> Vec<T> {
let vec_te: TypeEq<Vec<u32>, Vec<T>> = te.project::<GVec>();
vec_te.to_right(vec![3, 5, 8])
}
// Declares `GVec`, a type-level function from `T` to `Vec<T>`
typewit::type_fn!{
struct GVec;
impl<T> T => Vec<T>
}
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub const fn unmap<F>(
self,
func: F,
) -> TypeEq<<F as RevTypeFn<L>>::Arg, <F as RevTypeFn<R>>::Arg>
pub const fn unmap<F>( self, func: F, ) -> TypeEq<<F as RevTypeFn<L>>::Arg, <F as RevTypeFn<R>>::Arg>
Maps the type arguments of this TypeEq
by using the reversed
version of the F
type-level function.
Use this function over unproject
if you want the type of the passed in function to be inferred.
§Example
use typewit::{TypeEq, UncallFn};
assert_eq!(first_int(&[3, 5, 8, 13], TypeEq::NEW), 3);
const fn first_int<T, const N: usize>(
array: &[T; N],
te_slice: TypeEq<[T], [u8]>,
) -> u8 {
let te: TypeEq<T, u8> = te_slice.unmap(SliceFn);
let te_ref: TypeEq<&T, &u8> = te.in_ref();
*te_ref.to_right(&array[0])
}
typewit::inj_type_fn! {
struct SliceFn;
impl<T> T => [T]
}
Sourcepub const fn unproject<F>(
self,
) -> TypeEq<<F as RevTypeFn<L>>::Arg, <F as RevTypeFn<R>>::Arg>
pub const fn unproject<F>( self, ) -> TypeEq<<F as RevTypeFn<L>>::Arg, <F as RevTypeFn<R>>::Arg>
Maps the type arguments of this TypeEq
by using the reversed
version of the F
type-level function.
Use this function over unmap
if you want to specify the type of the passed in function explicitly.
§Example
use typewit::TypeEq;
use std::ops::{Range, RangeInclusive as RangeInc};
assert_eq!(usize_bounds(3..=5, TypeEq::NEW), (3, 5));
const fn usize_bounds<T>(
range: RangeInc<T>,
te_range: TypeEq<Range<T>, Range<usize>>,
) -> (usize, usize) {
let te: TypeEq<T, usize> = te_range.unproject::<RangeFn>();
let te_range_inc: TypeEq<RangeInc<T>, RangeInc<usize>> = te.project::<RangeIncFn>();
let range: RangeInc<usize> = te_range_inc.to_right(range);
(*range.start(), *range.end())
}
typewit::inj_type_fn! {
struct RangeFn;
impl<T> T => Range<T>
}
typewit::inj_type_fn! {
struct RangeIncFn;
impl<T> T => RangeInc<T>
}
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub const fn in_ref<'a>(self) -> TypeEq<&'a L, &'a R>
pub const fn in_ref<'a>(self) -> TypeEq<&'a L, &'a R>
Converts a TypeEq<L, R>
to TypeEq<&L, &R>
§Example
use typewit::{MakeTypeWitness, TypeEq};
assert_eq!(get::<u8>(), &3);
assert_eq!(get::<str>(), "hello");
const fn get<R: ?Sized>() -> &'static R
where
Returned<R>: MakeTypeWitness
{
match MakeTypeWitness::MAKE {
// `te` is a `TypeEq<R, u8>`
Returned::U8(te) => te.in_ref().to_left(&3),
// `te` is a `TypeEq<R, str>`
Returned::Str(te) => te.in_ref().to_left("hello"),
}
}
typewit::simple_type_witness! {
// declares the `enum Returned<R> {` type witness
enum Returned {
// this variant requires `R == u8`
U8 = u8,
// this variant requires `R == str`
Str = str,
}
}
Sourcepub fn in_mut<'a>(self) -> TypeEq<&'a mut L, &'a mut R>
pub fn in_mut<'a>(self) -> TypeEq<&'a mut L, &'a mut R>
Converts a TypeEq<L, R>
to TypeEq<&mut L, &mut R>
§Constness
This requires the "rust_1_83"
feature to be a const fn
.
§Example
Because this example calls in_mut
inside a const fn
,
it requires the "rust_1_83"
crate feature.
use typewit::{TypeEq, type_eq};
let foo = &mut Foo { bar: 10, baz: ['W', 'H', 'O'] };
*get_mut(foo, Field::Bar(type_eq())) *= 2;
assert_eq!(foo.bar, 20);
assert_eq!(*get_mut(foo, Field::Baz(type_eq())), ['W', 'H', 'O']);
const fn get_mut<R>(foo: &mut Foo, te: Field<R>) -> &mut R {
match te {
Field::Bar(te) => te.in_mut().to_left(&mut foo.bar),
Field::Baz(te) => te.in_mut().to_left(&mut foo.baz),
}
}
struct Foo {
bar: u8,
baz: [char; 3],
}
enum Field<R: ?Sized> {
Bar(TypeEq<R, u8>),
Baz(TypeEq<R, [char; 3]>),
}
Source§impl<L, R> TypeEq<L, R>
impl<L, R> TypeEq<L, R>
Sourcepub const fn in_array<const UL: usize, const UR: usize>(
self,
other: TypeEq<Usize<UL>, Usize<UR>>,
) -> TypeEq<[L; UL], [R; UR]>
pub const fn in_array<const UL: usize, const UR: usize>( self, other: TypeEq<Usize<UL>, Usize<UR>>, ) -> TypeEq<[L; UL], [R; UR]>
Combines TypeEq<L, R>
and TypeEq<Usize<UL>, Usize<UR>>
into TypeEq<[L; UL], [R; UR]>
§Alternative
For an alternative which allows passing any
BaseTypeWitness
for the length,
you can use methods::in_array
(requires the "rust_1_65"
feature)
§Example
motivation
The safe way to map an array in const fns(on stable Rust in 2023) is to create an array of the returned type with some dummy value, and then fill it in with the desired values.
Because the function in this example takes a [T; LEN]
where the T
is generic,
it copies the first element of the input array to initialize the returned array,
so we must handle empty arrays,
but trying to return an empty array the naive way
if LEN == 0 {
return [];
}
does not work
error[E0308]: mismatched types
--> src/type_eq.rs:827:16
|
4 | const fn map_wrapping<T: Copy, const LEN: usize>(arr: [T; LEN]) -> [Wrapping<T>; LEN] {
| ------------------ expected `[Wrapping<T>; LEN]` because of return type
5 | if LEN == 0 {
6 | return [];
| ^^ expected `LEN`, found `0`
|
= note: expected array `[Wrapping<T>; LEN]`
found array `[_; 0]`
This example demonstrates how in_array
allows one to return an empty array:
(this example requires Rust 1.61.0, because it uses trait bounds in const fns)
use typewit::{const_marker::Usize, TypeCmp, TypeEq};
use std::num::Wrapping;
assert_eq!(map_wrapping([""; 0]), []);
assert_eq!(map_wrapping([3, 5, 8]), [Wrapping(3), Wrapping(5), Wrapping(8)]);
const fn map_wrapping<T: Copy, const LEN: usize>(arr: [T; LEN]) -> [Wrapping<T>; LEN] {
// `teq` is a `TypeEq<Usize<LEN>, Usize<0>>`
if let TypeCmp::Eq(teq) = Usize::<LEN>.equals(Usize::<0>) {
return TypeEq::new::<Wrapping<T>>()
.in_array(teq) // `TypeEq<[Wrapping<T>; LEN], [Wrapping<T>; 0]>`
.to_left([]);
}
let mut ret = [Wrapping(arr[0]); LEN];
let mut i = 1;
while i < LEN {
ret[i] = Wrapping(arr[i]);
i += 1;
}
ret
}