Skip to content

Instantly share code, notes, and snippets.

@nrc

nrc/provider.rs Secret

Last active August 5, 2021 02:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nrc/293e88c0e796114ef181d1b2d5e1ac7e to your computer and use it in GitHub Desktop.
Save nrc/293e88c0e796114ef181d1b2d5e1ac7e to your computer and use it in GitHub Desktop.
pub mod provider {
use crate::any::TypeId;
use core::marker::PhantomData;
use core::mem;
// Internal structs for identifying classes of types. These are never instantiated and are only
// used for their type ids.
// Note that this type can also identify `&'static T`, etc.
struct ValueTypeIdTag<T: 'static>(PhantomData<T>);
// The parameter type T is T in `&'a T`.
struct RefTypeIdTag<T: ?Sized + 'static>(PhantomData<T>);
// Could add RefMutTypeId, types for Cow, etc.
// An initialization helper, id identifies the requested type, result points at memory where the
// result is stored; this may be uninitialized.
// `'a` is a lower bound on the lifetime of a reference stored into self.result.
pub struct TypeIdentifiedInit<'a> {
id: TypeId,
// true only if self.result is initialized and a valid point to the type identified by self.id.
init: bool,
// SAFETY must not be read unless self.init == true.
result: *mut u8,
phantom: PhantomData<&'a u8>,
}
impl<'a> TypeIdentifiedInit<'a> {
// If !self.init and T matches self.id, then execute `f` and store the result in self.result.
// Supports builder pattern usage.
pub fn set_if_uninit_with<T: 'static>(&mut self, f: impl Fn() -> T) -> &mut Self {
if !self.init && TypeId::of::<ValueTypeIdTag<T>>() == self.id {
unsafe {
// SAFETY: the type id check guarantees that the cast to `*mut T` is valid, we
// only write into the pointer, without reading or dropping contents.
(self.result as *mut T).write(f());
}
self.init = true;
}
self
}
pub fn set_if_uninit_ref<T: ?Sized + 'static>(&mut self, r: &'a T) -> &mut Self {
if !self.init && TypeId::of::<RefTypeIdTag<T>>() == self.id {
unsafe {
// SAFETY: in addition to the safety invariants for the value version above, we
// track `'a` in self to ensure that we only accept references here which can be
// assured to be valid for a given lifetime.
(self.result as *mut &'a T).write(r);
}
self.init = true;
}
self
}
}
pub trait Provider {
// The constrained lifetime here means that provided references can only be references to
// fields on self (or that are guaranteed to live longer than self).
fn provide<'a>(&'a self, _: &mut TypeIdentifiedInit<'a>);
}
pub fn get<T: 'static>(provider: &(impl Provider + ?Sized)) -> Option<T> {
let mut result = mem::MaybeUninit::<T>::uninit();
let mut tagged = TypeIdentifiedInit {
id: TypeId::of::<ValueTypeIdTag<T>>(),
init: false,
result: result.as_mut_ptr() as *mut u8,
phantom: PhantomData,
};
provider.provide(&mut tagged);
if tagged.init {
Some(unsafe { result.assume_init() })
} else {
None
}
}
pub fn get_ref<'a, T: ?Sized + 'static>(
provider: &'a (impl Provider + ?Sized),
) -> Option<&'a T> {
let mut result = mem::MaybeUninit::<&'a T>::uninit();
let mut tagged = TypeIdentifiedInit {
id: TypeId::of::<RefTypeIdTag<T>>(),
init: false,
result: result.as_mut_ptr() as *mut u8,
phantom: PhantomData,
};
provider.provide(&mut tagged);
if tagged.init {
Some(unsafe { result.assume_init() })
} else {
None
}
}
}
// Demonstrates how a client library uses the provider API by extending the Provider trait and delegating to
// provider functions.
pub mod error {
use crate::provider::{self, Provider, TypeIdentifiedInit};
use core::fmt::Debug;
pub trait MyError: Provider + Debug {
fn provide_context<'a>(&'a self, _: &mut TypeIdentifiedInit<'a>) {}
}
impl<T: MyError> Provider for T {
fn provide<'a>(&'a self, init: &mut TypeIdentifiedInit<'a>) {
self.provide_context(init);
}
}
impl dyn MyError {
pub fn context_ref<T: 'static + ?Sized>(&self) -> Option<&T> {
provider::get_ref(self)
}
pub fn context<T: 'static>(&self) -> Option<T> {
provider::get(self)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
pub struct ConcreteError {
name: String,
array: Vec<String>,
}
impl error::MyError for ConcreteError {
fn provide_context<'a>(&'a self, result: &mut provider::TypeIdentifiedInit<'a>) {
// Can't reference s because it doesn't live long enough.
// let s = "foo".to_owned();
result
.set_if_uninit_with(|| "Hello!".to_owned())
.set_if_uninit_ref(&*self.name)
// .set_if_uninit_ref(&s)
.set_if_uninit_ref::<[String]>(&self.array)
.set_if_uninit_with(|| "Boo!");
}
}
#[test]
fn it_works() {
let e: &dyn error::MyError = &ConcreteError {
name: "Bob".to_owned(),
array: vec![],
};
let s: String = e.context().unwrap();
assert_eq!(&s, "Hello!");
assert!(e.context::<i32>().is_none());
let s: &str = e.context_ref::<str>().unwrap();
assert_eq!(s, "Bob");
let a: &[String] = e.context_ref().unwrap();
assert_eq!(a.len(), 0);
let s = e.context::<&str>().unwrap();
assert_eq!(s, "Boo!");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment