extern crate alloc;
use core::any::TypeId;
pub trait Provider {
fn provide<'a>(&'a self, demand: &mut Demand<'a>);
}
pub fn request_value<'a, T, P>(provider: &'a P) -> Option<T>
where
T: 'static,
P: Provider + ?Sized,
{
request_by_type_tag::<'a, tags::Value<T>, P>(provider)
}
pub fn request_ref<'a, T, P>(provider: &'a P) -> Option<&'a T>
where
T: 'static + ?Sized,
P: Provider + ?Sized,
{
request_by_type_tag::<'a, tags::Ref<tags::MaybeSizedValue<T>>, P>(provider)
}
fn request_by_type_tag<'a, I, P>(provider: &'a P) -> Option<I::Reified>
where
I: tags::Type<'a>,
P: Provider + ?Sized,
{
let mut tagged = TaggedOption::<'a, I>(None);
provider.provide(tagged.as_demand());
tagged.0
}
#[repr(transparent)]
pub struct Demand<'a>(dyn Erased<'a> + 'a);
impl<'a> Demand<'a> {
fn new<'b>(erased: &'b mut (dyn Erased<'a> + 'a)) -> &'b mut Demand<'a> {
unsafe { &mut *(erased as *mut dyn Erased as *mut Demand) }
}
pub fn provide_value<T, F>(&mut self, fulfil: F) -> &mut Self
where
T: 'static,
F: FnOnce() -> T,
{
self.provide_with::<tags::Value<T>, F>(fulfil)
}
pub fn provide_ref<T: ?Sized + 'static>(&mut self, value: &'a T) -> &mut Self {
self.provide::<tags::Ref<tags::MaybeSizedValue<T>>>(value)
}
fn provide<I>(&mut self, value: I::Reified) -> &mut Self
where
I: tags::Type<'a>,
{
if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::<I>() {
res.0 = Some(value);
}
self
}
fn provide_with<I, F>(&mut self, fulfil: F) -> &mut Self
where
I: tags::Type<'a>,
F: FnOnce() -> I::Reified,
{
if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::<I>() {
res.0 = Some(fulfil());
}
self
}
}
mod tags {
use core::marker::PhantomData;
pub trait Type<'a>: Sized + 'static {
type Reified: 'a;
}
pub trait MaybeSizedType<'a>: Sized + 'static {
type Reified: 'a + ?Sized;
}
impl<'a, T: Type<'a>> MaybeSizedType<'a> for T {
type Reified = T::Reified;
}
#[derive(Debug)]
pub struct Value<T: 'static>(PhantomData<T>);
impl<'a, T: 'static> Type<'a> for Value<T> {
type Reified = T;
}
#[derive(Debug)]
pub struct MaybeSizedValue<T: ?Sized + 'static>(PhantomData<T>);
impl<'a, T: ?Sized + 'static> MaybeSizedType<'a> for MaybeSizedValue<T> {
type Reified = T;
}
#[derive(Debug)]
pub struct Ref<I>(PhantomData<I>);
impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref<I> {
type Reified = &'a I::Reified;
}
}
#[repr(transparent)]
struct TaggedOption<'a, I: tags::Type<'a>>(Option<I::Reified>);
impl<'a, I: tags::Type<'a>> TaggedOption<'a, I> {
fn as_demand(&mut self) -> &mut Demand<'a> {
Demand::new(self as &mut (dyn Erased<'a> + 'a))
}
}
trait Erased<'a>: 'a {
fn tag_id(&self) -> TypeId;
}
impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> {
fn tag_id(&self) -> TypeId {
TypeId::of::<I>()
}
}
impl<'a> dyn Erased<'a> {
#[inline]
fn downcast_mut<I>(&mut self) -> Option<&mut TaggedOption<'a, I>>
where
I: tags::Type<'a>,
{
if self.tag_id() == TypeId::of::<I>() {
Some(unsafe { &mut *(self as *mut Self).cast::<TaggedOption<'a, I>>() })
} else {
None
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::{borrow::ToOwned, boxed::Box, string::String};
use super::*;
struct SomeConcreteType {
some_string: String,
}
impl Provider for SomeConcreteType {
fn provide<'a>(&'a self, demand: &mut Demand<'a>) {
demand
.provide_ref::<String>(&self.some_string)
.provide_ref::<str>(&self.some_string)
.provide_value::<String, _>(|| "bye".to_owned());
}
}
#[test]
fn test_provider() {
let obj: &dyn Provider = &SomeConcreteType {
some_string: "hello".to_owned(),
};
assert_eq!(&**request_ref::<String, _>(obj).unwrap(), "hello");
assert_eq!(&*request_value::<String, _>(obj).unwrap(), "bye");
assert_eq!(request_value::<u8, _>(obj), None);
}
#[test]
fn test_provider_boxed() {
let obj: Box<dyn Provider> = Box::new(SomeConcreteType {
some_string: "hello".to_owned(),
});
assert_eq!(&**request_ref::<String, _>(&*obj).unwrap(), "hello");
assert_eq!(&*request_value::<String, _>(&*obj).unwrap(), "bye");
assert_eq!(request_value::<u8, _>(&*obj), None);
}
#[test]
fn test_provider_concrete() {
let obj = SomeConcreteType {
some_string: "hello".to_owned(),
};
assert_eq!(&**request_ref::<String, _>(&obj).unwrap(), "hello");
assert_eq!(&*request_value::<String, _>(&obj).unwrap(), "bye");
assert_eq!(request_value::<u8, _>(&obj), None);
}
trait OtherTrait: Provider {}
impl OtherTrait for SomeConcreteType {}
impl dyn OtherTrait {
fn get_ref<T: 'static + ?Sized>(&self) -> Option<&T> {
request_ref::<T, _>(self)
}
}
#[test]
fn test_provider_intermediate() {
let obj: &dyn OtherTrait = &SomeConcreteType {
some_string: "hello".to_owned(),
};
assert_eq!(obj.get_ref::<str>().unwrap(), "hello");
}
}