3 stable releases
1.1.0 | Jul 16, 2024 |
---|---|
1.0.1 | Jul 13, 2024 |
#811 in Rust patterns
Used in tisel
15KB
123 lines
cisness
Crate containing a type to "live-witness" that two types are the same ("cismutable").
In particular, it lets you assert that a codepath will not actually be executed at runtime if the types are NOT the same (and panic if it does), which is useful for - for instance - checking types via TypeId
, and selecting operations with type-specific output where you want the type specificity can be transferred to the user.
The key structure you want to use this library, is LiveWitness
. Using it "bare" from this library is somewhat clunky, but it can be used to build better abstractions (the author is working on a small crate called tisel
that isn't published yet that will use this as a basis).
Example
Here's an example of how to use LiveWitness
to invoke a trait method that has an associated type that can be output. Of course, in such a simple case, this isn't very useful, but it is much more useful in other situations.
use cisness::LiveWitness;
use std::{collections::HashMap, any::{TypeId, Any}};
pub trait PartyMember {
type Age: core::fmt::Display;
fn get_age(&self) -> Self::Age;
}
#[derive(Debug, Clone)]
pub struct Human {
pub name: String,
pub age: u32,
}
impl PartyMember for Human {
type Age = u32;
fn get_age(&self) -> Self::Age {
self.age
}
}
#[derive(Debug, Clone)]
pub struct Dragon {
pub name: String
}
impl PartyMember for Dragon {
type Age = &'static str;
fn get_age(&self) -> Self::Age {
"Unknowable and Eldritch"
}
}
/// Party that stores the vocabulary "member" types in one hashmap dynamically.
/// Note that clearly, in this case, there are better ways to do it - but often that would not be the case.
#[derive(Default)]
pub struct AdventuringParty {
// Maps the `Member` type id to a vector of members of that type.
members: HashMap<TypeId, Box<dyn Any>>
}
impl AdventuringParty {
pub fn add_member<M: Any>(&mut self, member: M) {
let new_member_typeid = TypeId::of::<M>();
self.members
.entry(new_member_typeid)
.or_insert_with(|| Box::new(Vec::<M>::new()))
.downcast_mut::<Vec::<M>>()
.expect("<typeid> is mapped to Vec<type for typeid> and we just inited")
.push(member);
}
// Note here - the better way to do this specific example would be to put a trait criteria on
// the `M` generic requiring `Ord`, and doing downcasting. However, for illustration purposes, we'll
// be listing out supported types and manually implementing them, returning `None` for unorderable ages.
pub fn get_oldest_age_of_type<M: Any + PartyMember>(&self) -> Option<<M as PartyMember>::Age>
{
// Note that you could do this with `Any::downcast_ref`. However, that doesn't work if you can't
// get a value of `M`, and it doesn't work as well for more complex cases.
// To illustrate, we're going to use TypeId matching, even if for this case it would work better not to do that.
let type_id = TypeId::of::<M>();
match type_id {
t if t == TypeId::of::<Human>() => {
// Note here that we don't have to directly witness the type of the generic parameter, we can instead witness
// the output (if we know it), or a vector.
// In this case, we're asserting that we'll only run through this code if the output type is the same as
// `Option<Human::Age>` - this lets us do "magical" coercions that will panic if we've made an error.
let w = LiveWitness::<Option<<Human as PartyMember>::Age>, Option<M::Age>>::only_executed_if_same();
let output = self.members.get(&t)
.and_then(|v| v.downcast_ref::<Vec<Human>>())
.map(|v| v.iter().map(PartyMember::get_age).max()).flatten();
// Note here - our witness lets us turn this to the generic output, and will typecheck even if `M` != `Human` - because we don't go down
// this path, it's fine.
w.owned(output)
},
t if t == TypeId::of::<Dragon>() => {
let w = LiveWitness::<Option<<Dragon as PartyMember>::Age>, Option<M::Age>>::only_executed_if_same();
if !self.members.get(&t).is_some() { return None };
// Note how at no point here did we actually have to downcast any values.
let output = Some("[ELDRICH MEMETIC HAZARD]");
w.owned(output)
}
_ => None
}
}
}
The Name
Yes, it's intentional. The author is trans 🏳️⚧️, and finds it funny.
Dependencies
~18KB