diff --git a/Cargo.toml b/Cargo.toml index 6fefd67..9a0174b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aerosol" -version = "1.0.0-alpha.4" +version = "1.0.0-alpha.5" authors = ["Diggory Blake "] edition = "2018" description = "Simple dependency injection for Rust" @@ -23,6 +23,7 @@ axum = { version = "0.6", optional = true } tracing = { version = "0.1", optional = true } thiserror = { version = "1.0", optional = true } anyhow = { version = "1.0" } +frunk = "0.4.2" [dev-dependencies] tokio = { version = "1.0", features = ["macros"] } diff --git a/src/async_.rs b/src/async_.rs index 4520955..58911f7 100644 --- a/src/async_.rs +++ b/src/async_.rs @@ -5,20 +5,18 @@ use std::{ task::{Context, Poll}, }; -use crate::{ - resource::{unwrap_resource, Resource}, - slot::SlotDesc, - state::Aerosol, -}; +use frunk::prelude::HList; + +use crate::{resource::Resource, slot::SlotDesc, state::Aerosol}; -pub(crate) struct WaitForSlot { - state: Aerosol, +pub(crate) struct WaitForSlot { + state: Aerosol, wait_index: Option, insert_placeholder: bool, phantom: PhantomData T>, } -impl Future for WaitForSlot { +impl Future for WaitForSlot { type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -28,11 +26,11 @@ impl Future for WaitForSlot { } } -impl Aerosol { +impl Aerosol { pub(crate) fn wait_for_slot_async( &self, insert_placeholder: bool, - ) -> WaitForSlot { + ) -> WaitForSlot { WaitForSlot { state: self.clone(), wait_index: None, @@ -48,30 +46,12 @@ impl Aerosol { SlotDesc::Placeholder => self.wait_for_slot_async::(false).await, } } - /// Get an instance of `T` from the AppState, and panic if not found. - /// This function does not attempt to construct `T` if it does not exist. - pub async fn get_async(&self) -> T { - unwrap_resource(self.try_get_async().await) - } } #[cfg(test)] mod tests { use super::*; - #[tokio::test] - async fn get_with() { - let state = Aerosol::new().with(42); - assert_eq!(state.get_async::().await, 42); - } - - #[tokio::test] - async fn get_inserted() { - let state = Aerosol::new(); - state.insert(42); - assert_eq!(state.get_async::().await, 42); - } - #[tokio::test] async fn try_get_some() { let state = Aerosol::new().with(42); diff --git a/src/async_constructible.rs b/src/async_constructible.rs index 73dae72..e9ed033 100644 --- a/src/async_constructible.rs +++ b/src/async_constructible.rs @@ -1,6 +1,7 @@ use std::{any::Any, sync::Arc}; use async_trait::async_trait; +use frunk::prelude::HList; use crate::{ resource::{unwrap_constructed, Resource}, @@ -111,14 +112,14 @@ impl_async_constructible! { pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {} impl AsyncConstructibleResource for T {} -impl Aerosol { +impl Aerosol { /// Try to get or construct an instance of `T` asynchronously. Requires feature `async`. pub async fn try_obtain_async(&self) -> Result { match self.try_get_slot() { Some(SlotDesc::Filled(x)) => Ok(x), Some(SlotDesc::Placeholder) | None => match self.wait_for_slot_async::(true).await { Some(x) => Ok(x), - None => match T::construct_async(self).await { + None => match T::construct_async(self.as_ref()).await { Ok(x) => { self.fill_placeholder::(x.clone()); Ok(x) @@ -139,7 +140,7 @@ impl Aerosol { pub async fn try_init_async(&self) -> Result<(), T::Error> { match self.wait_for_slot_async::(true).await { Some(_) => Ok(()), - None => match T::construct_async(self).await { + None => match T::construct_async(self.as_ref()).await { Ok(x) => { self.fill_placeholder::(x); Ok(()) @@ -340,6 +341,6 @@ mod tests { async fn obtain_impl() { let state = Aerosol::new(); state.init_async::>().await; - state.get_async::>().await; + state.try_get_async::>().await.unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index dc20923..9214ccf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ //! //! Provides integrations with the `axum` web framework. See the `axum` module //! for more information. +pub use frunk; #[cfg(feature = "async")] mod async_; @@ -20,6 +21,7 @@ mod async_; mod async_constructible; #[cfg(feature = "axum")] pub mod axum; +mod macros; mod resource; mod slot; mod state; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..590265b --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,7 @@ +/// Define a custom `Aerosol` alias with a specific set of required types +#[macro_export] +macro_rules! Aero { + ($($tok:tt)*) => { + $crate::Aerosol<$crate::frunk::HList![$($tok)*]> + }; +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/src/state.rs b/src/state.rs index 58fb0e6..256396c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,11 @@ -use std::{any::Any, sync::Arc, task::Poll}; +use std::{any::Any, marker::PhantomData, sync::Arc, task::Poll}; use anymap::hashbrown::{Entry, Map}; +use frunk::{ + hlist::{HFoldRightable, Sculptor}, + prelude::HList, + HCons, HNil, Poly, +}; use parking_lot::RwLock; use crate::{ @@ -16,16 +21,67 @@ struct InnerAerosol { /// Stores a collection of resources keyed on resource type. /// Provides methods for accessing this collection. /// Can be cheaply cloned. -#[derive(Debug, Clone, Default)] -pub struct Aerosol { +#[derive(Debug)] +#[repr(transparent)] +pub struct Aerosol { inner: Arc>, + phantom: PhantomData>, } impl Aerosol { /// Construct a new instance of the type with no initial resources. pub fn new() -> Self { - Self::default() + Self { + inner: Default::default(), + phantom: PhantomData, + } + } +} + +impl Clone for Aerosol { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + phantom: PhantomData, + } + } +} + +struct AerosolBuilderFolder; +impl frunk::Func<(Aerosol, T)> for AerosolBuilderFolder { + type Output = Aerosol>; + fn call((aero, value): (Aerosol, T)) -> Self::Output { + aero.with(value) + } +} + +#[doc(hidden)] +pub trait HTestable { + fn test(aero: &Aerosol) -> bool; +} + +impl HTestable for HNil { + fn test(_aero: &Aerosol) -> bool { + true + } +} + +impl HTestable for HCons { + fn test(aero: &Aerosol) -> bool { + aero.has::() && T::test(aero) + } +} + +impl< + R: Default + HFoldRightable, Aerosol, Output = Aerosol> + HList, + > Default for Aerosol +{ + fn default() -> Self { + R::default().foldr(Poly(AerosolBuilderFolder), Aerosol::new()) } +} + +impl Aerosol { /// Directly insert a resource into the collection. Panics if a resource of the /// same type already exists. pub fn insert(&self, value: T) { @@ -38,10 +94,76 @@ impl Aerosol { } /// Builder method equivalent to calling `insert()` but can be chained. - pub fn with(self, value: T) -> Self { + pub fn with(self, value: T) -> Aerosol> { self.insert(value); - self + Aerosol { + inner: self.inner, + phantom: PhantomData, + } + } + + /// Convert into a different variant of the Aerosol type. The new variant must + /// not require any resources which are not required as part of this type. + pub fn into(self) -> Aerosol + where + R: Sculptor, + { + Aerosol { + inner: self.inner, + phantom: PhantomData, + } } + + /// Reborrow as a different variant of the Aerosol type. The new variant must + /// not require any resources which are not required as part of this type. + #[allow(clippy::should_implement_trait)] + pub fn as_ref(&self) -> &Aerosol + where + R: Sculptor, + { + // Safety: all Aerosol variants are `#[repr(transparent)]` wrappers around + // the same concrete type. + unsafe { std::mem::transmute(self) } + } + + /// Try to convert into a different variant of the Aerosol type. Returns the + /// original type if one or more of the required resources are not fully + /// constructed. + pub fn try_into(self) -> Result, Self> { + if R2::test(&self) { + Ok(Aerosol { + inner: self.inner, + phantom: PhantomData, + }) + } else { + Err(self) + } + } + + /// Try to convert into a different variant of the Aerosol type. Returns + /// `None` if one or more of the required resources are not fully + /// constructed. + pub fn try_as_ref(&self) -> Option<&Aerosol> { + if R2::test(self) { + Some( + // Safety: all Aerosol variants are `#[repr(transparent)]` wrappers around + // the same concrete type. + unsafe { std::mem::transmute(self) }, + ) + } else { + None + } + } + + /// Check if a resource with a specific type is fully constructed in this + /// aerosol instance + pub fn has(&self) -> bool { + matches!( + self.inner.read().items.get::>(), + Some(Slot::Filled(_)) + ) + } + pub(crate) fn try_get_slot(&self) -> Option> { self.inner.read().items.get().map(Slot::desc) } @@ -89,8 +211,22 @@ impl Aerosol { } } +impl AsRef for Aerosol { + fn as_ref(&self) -> &Aerosol { + Aerosol::as_ref(self) + } +} + +impl From>> for Aerosol { + fn from(value: Aerosol>) -> Self { + value.into() + } +} + #[cfg(test)] mod tests { + use crate::Aero; + use super::*; #[test] @@ -105,4 +241,18 @@ mod tests { let state = Aerosol::new().with(13); state.insert(42); } + + #[test] + fn default() { + let state: Aero![i32] = Aerosol::default(); + state.insert("Hello, world!"); + } + + #[test] + fn convert() { + let state: Aero![i32, String, f32] = Aerosol::default(); + state.insert("Hello, world!"); + let state2: Aero![f32, String] = state.into(); + let _state3: Aero![i32, String, f32] = state2.try_into().unwrap(); + } } diff --git a/src/sync.rs b/src/sync.rs index bfaed41..4ed4b86 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1,5 +1,7 @@ use std::{task::Poll, thread}; +use frunk::{hlist::Plucker, prelude::HList}; + use crate::{ resource::{unwrap_resource, Resource}, slot::SlotDesc, @@ -16,7 +18,7 @@ pub fn safe_park() { std::thread::park(); } -impl Aerosol { +impl Aerosol { /// Synchronously wait for the slot for `T` to not have a placeholder. /// Returns immediately if there is no `T` present, or if `T`'s slot is filled. pub(crate) fn wait_for_slot(&self, insert_placeholder: bool) -> Option { @@ -37,9 +39,11 @@ impl Aerosol { SlotDesc::Placeholder => self.wait_for_slot::(false), } } - /// Get an instance of `T` from the AppState, and panic if not found. - /// This function does not attempt to construct `T` if it does not exist. - pub fn get(&self) -> T { + /// Get an instance of `T` from the AppState which is statically known to be present. + pub fn get(&self) -> T + where + R: Plucker, + { unwrap_resource(self.try_get()) } } @@ -51,14 +55,7 @@ mod tests { #[test] fn get_with() { let state = Aerosol::new().with(42); - assert_eq!(state.get::(), 42); - } - - #[test] - fn get_inserted() { - let state = Aerosol::new(); - state.insert(42); - assert_eq!(state.get::(), 42); + assert_eq!(state.get::(), 42); } #[test] diff --git a/src/sync_constructible.rs b/src/sync_constructible.rs index bffacfd..8572278 100644 --- a/src/sync_constructible.rs +++ b/src/sync_constructible.rs @@ -1,5 +1,7 @@ use std::{any::Any, sync::Arc}; +use frunk::prelude::HList; + use crate::{ resource::{unwrap_constructed, Resource}, slot::SlotDesc, @@ -90,14 +92,14 @@ impl_constructible! { pub trait ConstructibleResource: Resource + IndirectlyConstructible {} impl ConstructibleResource for T {} -impl Aerosol { +impl Aerosol { /// Try to get or construct an instance of `T`. pub fn try_obtain(&self) -> Result { match self.try_get_slot() { Some(SlotDesc::Filled(x)) => Ok(x), Some(SlotDesc::Placeholder) | None => match self.wait_for_slot::(true) { Some(x) => Ok(x), - None => match T::construct(self) { + None => match T::construct(self.as_ref()) { Ok(x) => { self.fill_placeholder::(x.clone()); Ok(x) @@ -118,7 +120,7 @@ impl Aerosol { pub fn try_init(&self) -> Result<(), T::Error> { match self.wait_for_slot::(true) { Some(_) => Ok(()), - None => match T::construct(self) { + None => match T::construct(self.as_ref()) { Ok(x) => { self.fill_placeholder::(x); Ok(()) @@ -264,6 +266,6 @@ mod tests { fn obtain_impl() { let state = Aerosol::new(); state.init::>(); - state.get::>(); + state.try_get::>().unwrap(); } }