aboutsummaryrefslogtreecommitdiff
path: root/src/private
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2022-11-19 15:45:12 +0100
committerHampusM <hampus@hampusmat.com>2022-11-19 15:45:12 +0100
commit9f27a925bd323e8e0864bedeb33a3c6953517ea1 (patch)
treeea5d8faaed82c58fa037fa377173bb365e1cd697 /src/private
parentd99cbf9fa95856cbc14a3217e1cd3f13aeb2e0b3 (diff)
refactor: reorganize non-public API items
Diffstat (limited to 'src/private')
-rw-r--r--src/private/any_factory.rs11
-rw-r--r--src/private/cast/arc.rs93
-rw-r--r--src/private/cast/boxed.rs87
-rw-r--r--src/private/cast/error.rs20
-rw-r--r--src/private/cast/mod.rs273
-rw-r--r--src/private/cast/rc.rs87
-rw-r--r--src/private/castable_factory/blocking.rs153
-rw-r--r--src/private/castable_factory/mod.rs4
-rw-r--r--src/private/castable_factory/threadsafe.rs172
-rw-r--r--src/private/factory.rs23
-rw-r--r--src/private/mod.rs15
11 files changed, 938 insertions, 0 deletions
diff --git a/src/private/any_factory.rs b/src/private/any_factory.rs
new file mode 100644
index 0000000..bdd68a6
--- /dev/null
+++ b/src/private/any_factory.rs
@@ -0,0 +1,11 @@
+//! Interface for any factory to ever exist.
+
+use std::fmt::Debug;
+
+use crate::private::cast::{CastFrom, CastFromSync};
+
+/// Interface for any factory to ever exist.
+pub trait AnyFactory: CastFrom + Debug {}
+
+/// Interface for any threadsafe factory to ever exist.
+pub trait AnyThreadsafeFactory: CastFromSync + Debug {}
diff --git a/src/private/cast/arc.rs b/src/private/cast/arc.rs
new file mode 100644
index 0000000..7ea8b49
--- /dev/null
+++ b/src/private/cast/arc.rs
@@ -0,0 +1,93 @@
+//! Originally from Intertrait by CodeChain
+//!
+//! <https://github.com/CodeChain-io/intertrait>
+//! <https://crates.io/crates/intertrait/0.2.2>
+//!
+//! Licensed under either of
+//!
+//! Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
+//! MIT license (LICENSE-MIT or <http://opensource.org/licenses/MIT>)
+//!
+//! at your option.
+use std::any::type_name;
+use std::sync::Arc;
+
+use crate::private::cast::error::CastError;
+use crate::private::cast::{get_caster, CastFromSync};
+
+pub trait CastArc
+{
+ /// Casts an `Arc` with `Self` into an `Arc` with `Dest`.
+ fn cast<Dest: ?Sized + 'static>(self: Arc<Self>) -> Result<Arc<Dest>, CastError>;
+}
+
+/// A blanket implementation of `CastArc` for traits extending `CastFrom`, `Sync`, and
+/// `Send`.
+impl<CastFromSelf: ?Sized + CastFromSync> CastArc for CastFromSelf
+{
+ fn cast<Dest: ?Sized + 'static>(self: Arc<Self>) -> Result<Arc<Dest>, CastError>
+ {
+ let caster =
+ get_caster::<Dest>((*self).type_id()).map_err(CastError::GetCasterFailed)?;
+
+ let cast_arc = caster
+ .opt_cast_arc
+ .ok_or(CastError::NotArcCastable(type_name::<Dest>()))?;
+
+ cast_arc(self.arc_any()).map_err(|err| CastError::CastFailed {
+ source: err,
+ from: type_name::<Self>(),
+ to: type_name::<Dest>(),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::any::Any;
+ use std::fmt::{Debug, Display};
+ use std::sync::Arc;
+
+ use super::*;
+ use crate::test_utils::subjects;
+
+ #[test]
+ fn can_cast_arc()
+ {
+ let concrete_ninja = Arc::new(subjects::Ninja);
+
+ let abstract_ninja: Arc<dyn subjects::INinja> = concrete_ninja;
+
+ let debug_ninja_result = abstract_ninja.cast::<dyn Debug>();
+
+ assert!(debug_ninja_result.is_ok());
+ }
+
+ #[test]
+ fn cannot_cast_arc_wrong()
+ {
+ let concrete_ninja = Arc::new(subjects::Ninja);
+
+ let abstract_ninja: Arc<dyn subjects::INinja> = concrete_ninja;
+
+ let display_ninja_result = abstract_ninja.cast::<dyn Display>();
+
+ assert!(matches!(
+ display_ninja_result,
+ Err(CastError::GetCasterFailed(_))
+ ));
+ }
+
+ #[test]
+ fn can_cast_arc_from_any()
+ {
+ let concrete_ninja = Arc::new(subjects::Ninja);
+
+ let any_ninja: Arc<dyn Any + Send + Sync> = concrete_ninja;
+
+ let debug_ninja_result = any_ninja.cast::<dyn Debug>();
+
+ assert!(debug_ninja_result.is_ok());
+ }
+}
diff --git a/src/private/cast/boxed.rs b/src/private/cast/boxed.rs
new file mode 100644
index 0000000..074346c
--- /dev/null
+++ b/src/private/cast/boxed.rs
@@ -0,0 +1,87 @@
+//! Originally from Intertrait by CodeChain
+//!
+//! <https://github.com/CodeChain-io/intertrait>
+//! <https://crates.io/crates/intertrait/0.2.2>
+//!
+//! Licensed under either of
+//!
+//! Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
+//! MIT license (LICENSE-MIT or <http://opensource.org/licenses/MIT>)
+//!
+//! at your option.
+
+use std::any::type_name;
+
+use crate::private::cast::error::CastError;
+use crate::private::cast::{get_caster, CastFrom};
+
+pub trait CastBox
+{
+ /// Casts a `Box` with `Self` into a `Box` with `Dest`.
+ fn cast<Dest: ?Sized + 'static>(self: Box<Self>) -> Result<Box<Dest>, CastError>;
+}
+
+/// A blanket implementation of `CastBox` for traits extending `CastFrom`.
+impl<CastFromSelf: ?Sized + CastFrom> CastBox for CastFromSelf
+{
+ fn cast<Dest: ?Sized + 'static>(self: Box<Self>) -> Result<Box<Dest>, CastError>
+ {
+ let caster =
+ get_caster::<Dest>((*self).type_id()).map_err(CastError::GetCasterFailed)?;
+
+ (caster.cast_box)(self.box_any()).map_err(|err| CastError::CastFailed {
+ source: err,
+ from: type_name::<Self>(),
+ to: type_name::<Dest>(),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::any::Any;
+ use std::fmt::{Debug, Display};
+
+ use super::*;
+ use crate::test_utils::subjects;
+
+ #[test]
+ fn can_cast_box()
+ {
+ let concrete_ninja = Box::new(subjects::Ninja);
+
+ let abstract_ninja: Box<dyn subjects::INinja> = concrete_ninja;
+
+ let debug_ninja_result = abstract_ninja.cast::<dyn Debug>();
+
+ assert!(debug_ninja_result.is_ok());
+ }
+
+ #[test]
+ fn cannot_cast_box_wrong()
+ {
+ let concrete_ninja = Box::new(subjects::Ninja);
+
+ let abstract_ninja: Box<dyn subjects::INinja> = concrete_ninja;
+
+ let display_ninja_result = abstract_ninja.cast::<dyn Display>();
+
+ assert!(matches!(
+ display_ninja_result,
+ Err(CastError::GetCasterFailed(_))
+ ));
+ }
+
+ #[test]
+ fn can_cast_box_from_any()
+ {
+ let concrete_ninja = Box::new(subjects::Ninja);
+
+ let any_ninja: Box<dyn Any> = concrete_ninja;
+
+ let debug_ninja_result = any_ninja.cast::<dyn Debug>();
+
+ assert!(debug_ninja_result.is_ok());
+ }
+}
diff --git a/src/private/cast/error.rs b/src/private/cast/error.rs
new file mode 100644
index 0000000..c6ed01d
--- /dev/null
+++ b/src/private/cast/error.rs
@@ -0,0 +1,20 @@
+use crate::private::cast::{CasterError, GetCasterError};
+
+#[derive(thiserror::Error, Debug)]
+pub enum CastError
+{
+ #[error("Failed to get caster")]
+ GetCasterFailed(#[from] GetCasterError),
+
+ #[error("Failed to cast from {from} to {to}")]
+ CastFailed
+ {
+ #[source]
+ source: CasterError,
+ from: &'static str,
+ to: &'static str,
+ },
+
+ #[error("'{0}' can't be cast to an Arc")]
+ NotArcCastable(&'static str),
+}
diff --git a/src/private/cast/mod.rs b/src/private/cast/mod.rs
new file mode 100644
index 0000000..0b80057
--- /dev/null
+++ b/src/private/cast/mod.rs
@@ -0,0 +1,273 @@
+//! Originally from Intertrait by CodeChain
+//!
+//! <https://github.com/CodeChain-io/intertrait>
+//! <https://crates.io/crates/intertrait/0.2.2>
+//!
+//! Licensed under either of
+//!
+//! Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
+//! MIT license (LICENSE-MIT or <http://opensource.org/licenses/MIT>)
+//!
+//! at your option.
+use std::any::{Any, TypeId};
+use std::rc::Rc;
+use std::sync::Arc;
+
+use ahash::AHashMap;
+use linkme::distributed_slice;
+use once_cell::sync::Lazy;
+
+pub mod arc;
+pub mod boxed;
+pub mod error;
+pub mod rc;
+
+pub type BoxedCaster = Box<dyn Any + Send + Sync>;
+
+/// A distributed slice gathering constructor functions for [`Caster`]s.
+///
+/// A constructor function returns `TypeId` of a concrete type involved in the casting
+/// and a `Box` of a type or trait backed by a [`Caster`].
+#[distributed_slice]
+pub static CASTERS: [fn() -> (TypeId, BoxedCaster)] = [..];
+
+/// A `HashMap` mapping `TypeId` of a [`Caster`] to an instance of it.
+static CASTER_MAP: Lazy<AHashMap<(TypeId, TypeId), BoxedCaster>> = Lazy::new(|| {
+ CASTERS
+ .iter()
+ .map(|caster_fn| {
+ let (type_id, caster) = caster_fn();
+
+ ((type_id, (*caster).type_id()), caster)
+ })
+ .collect()
+});
+
+type CastBoxFn<Dest> = fn(from: Box<dyn Any>) -> Result<Box<Dest>, CasterError>;
+
+type CastRcFn<Dest> = fn(from: Rc<dyn Any>) -> Result<Rc<Dest>, CasterError>;
+
+type CastArcFn<Dest> =
+ fn(from: Arc<dyn Any + Sync + Send + 'static>) -> Result<Arc<Dest>, CasterError>;
+
+/// A `Caster` knows how to cast a type or trait to the type or trait `Dest`. Each
+/// `Caster` instance is specific to a concrete type. That is, it knows how to cast to
+/// single specific type or trait implemented by single specific type.
+pub struct Caster<Dest: ?Sized + 'static>
+{
+ /// Casts a `Box` holding a type or trait object for `Any` to another `Box` holding a
+ /// type or trait `Dest`.
+ pub cast_box: CastBoxFn<Dest>,
+
+ /// Casts an `Rc` holding a type or trait for `Any` to another `Rc` holding a type or
+ /// trait `Dest`.
+ pub cast_rc: CastRcFn<Dest>,
+
+ /// Casts an `Arc` holding a type or trait for `Any + Sync + Send + 'static` to
+ /// another `Arc` holding a type or trait for `Dest`.
+ pub opt_cast_arc: Option<CastArcFn<Dest>>,
+}
+
+impl<Dest: ?Sized + 'static> Caster<Dest>
+{
+ pub fn new(cast_box: CastBoxFn<Dest>, cast_rc: CastRcFn<Dest>) -> Caster<Dest>
+ {
+ Caster::<Dest> {
+ cast_box,
+ cast_rc,
+ opt_cast_arc: None,
+ }
+ }
+
+ #[allow(clippy::similar_names)]
+ pub fn new_sync(
+ cast_box: CastBoxFn<Dest>,
+ cast_rc: CastRcFn<Dest>,
+ cast_arc: CastArcFn<Dest>,
+ ) -> Caster<Dest>
+ {
+ Caster::<Dest> {
+ cast_box,
+ cast_rc,
+ opt_cast_arc: Some(cast_arc),
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum CasterError
+{
+ #[error("Failed to cast Box")]
+ CastBoxFailed,
+
+ #[error("Failed to cast Rc")]
+ CastRcFailed,
+
+ #[error("Failed to cast Arc")]
+ CastArcFailed,
+}
+
+/// Returns a `Caster<Dest>` from a concrete type with the id `type_id` to a type or trait
+/// `Dest`.
+fn get_caster<Dest: ?Sized + 'static>(
+ type_id: TypeId,
+) -> Result<&'static Caster<Dest>, GetCasterError>
+{
+ let any_caster = CASTER_MAP
+ .get(&(type_id, TypeId::of::<Caster<Dest>>()))
+ .ok_or(GetCasterError::NotFound)?;
+
+ any_caster
+ .downcast_ref::<Caster<Dest>>()
+ .ok_or(GetCasterError::DowncastFailed)
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum GetCasterError
+{
+ #[error("Caster not found")]
+ NotFound,
+
+ #[error("Failed to downcast caster")]
+ DowncastFailed,
+}
+
+/// `CastFrom` must be extended by a trait that wants to allow for casting into another
+/// trait.
+///
+/// It is used for obtaining a trait object for [`Any`] from a trait object for its
+/// sub-trait, and blanket implemented for all `Sized + Any + 'static` types.
+///
+/// # Examples
+/// ```ignore
+/// trait Source: CastFrom {
+/// ...
+/// }
+/// ```
+pub trait CastFrom: Any + 'static
+{
+ /// Returns a `Box` of `Any`, which is backed by the type implementing this trait.
+ fn box_any(self: Box<Self>) -> Box<dyn Any>;
+
+ /// Returns an `Rc` of `Any`, which is backed by the type implementing this trait.
+ fn rc_any(self: Rc<Self>) -> Rc<dyn Any>;
+}
+
+/// `CastFromSync` must be extended by a trait that is `Any + Sync + Send + 'static`
+/// and wants to allow for casting into another trait behind references and smart pointers
+/// especially including `Arc`.
+///
+/// It is used for obtaining a trait object for [`Any + Sync + Send + 'static`] from an
+/// object for its sub-trait, and blanket implemented for all `Sized + Sync + Send +
+/// 'static` types.
+///
+/// # Examples
+/// ```ignore
+/// trait Source: CastFromSync {
+/// ...
+/// }
+/// ```
+pub trait CastFromSync: CastFrom + Sync + Send + 'static
+{
+ fn arc_any(self: Arc<Self>) -> Arc<dyn Any + Sync + Send + 'static>;
+}
+
+impl<Source: Sized + Any + 'static> CastFrom for Source
+{
+ fn box_any(self: Box<Self>) -> Box<dyn Any>
+ {
+ self
+ }
+
+ fn rc_any(self: Rc<Self>) -> Rc<dyn Any>
+ {
+ self
+ }
+}
+
+impl CastFrom for dyn Any + 'static
+{
+ fn box_any(self: Box<Self>) -> Box<dyn Any>
+ {
+ self
+ }
+
+ fn rc_any(self: Rc<Self>) -> Rc<dyn Any>
+ {
+ self
+ }
+}
+
+impl<Source: Sized + Sync + Send + 'static> CastFromSync for Source
+{
+ fn arc_any(self: Arc<Self>) -> Arc<dyn Any + Sync + Send + 'static>
+ {
+ self
+ }
+}
+
+impl CastFrom for dyn Any + Sync + Send + 'static
+{
+ fn box_any(self: Box<Self>) -> Box<dyn Any>
+ {
+ self
+ }
+
+ fn rc_any(self: Rc<Self>) -> Rc<dyn Any>
+ {
+ self
+ }
+}
+
+impl CastFromSync for dyn Any + Sync + Send + 'static
+{
+ fn arc_any(self: Arc<Self>) -> Arc<dyn Any + Sync + Send + 'static>
+ {
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::any::TypeId;
+ use std::fmt::Debug;
+
+ use linkme::distributed_slice;
+
+ use super::*;
+ use crate::test_utils::subjects;
+
+ #[distributed_slice(super::CASTERS)]
+ static TEST_CASTER: fn() -> (TypeId, BoxedCaster) = create_test_caster;
+
+ fn create_test_caster() -> (TypeId, BoxedCaster)
+ {
+ let type_id = TypeId::of::<subjects::Ninja>();
+
+ let caster = Box::new(Caster::<dyn Debug> {
+ cast_box: |from| {
+ let concrete = from
+ .downcast::<subjects::Ninja>()
+ .map_err(|_| CasterError::CastBoxFailed)?;
+
+ Ok(concrete as Box<dyn Debug>)
+ },
+ cast_rc: |from| {
+ let concrete = from
+ .downcast::<subjects::Ninja>()
+ .map_err(|_| CasterError::CastRcFailed)?;
+
+ Ok(concrete as Rc<dyn Debug>)
+ },
+ opt_cast_arc: Some(|from| {
+ let concrete = from
+ .downcast::<subjects::Ninja>()
+ .map_err(|_| CasterError::CastArcFailed)?;
+
+ Ok(concrete as Arc<dyn Debug>)
+ }),
+ });
+ (type_id, caster)
+ }
+}
diff --git a/src/private/cast/rc.rs b/src/private/cast/rc.rs
new file mode 100644
index 0000000..11d137a
--- /dev/null
+++ b/src/private/cast/rc.rs
@@ -0,0 +1,87 @@
+//! Originally from Intertrait by CodeChain
+//!
+//! <https://github.com/CodeChain-io/intertrait>
+//! <https://crates.io/crates/intertrait/0.2.2>
+//!
+//! Licensed under either of
+//!
+//! Apache License, Version 2.0 (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
+//! MIT license (LICENSE-MIT or <http://opensource.org/licenses/MIT>)
+//!
+//! at your option.
+use std::any::type_name;
+use std::rc::Rc;
+
+use crate::private::cast::error::CastError;
+use crate::private::cast::{get_caster, CastFrom};
+
+pub trait CastRc
+{
+ /// Casts an `Rc` with `Self `into a `Rc` with `Dest`.
+ fn cast<Dest: ?Sized + 'static>(self: Rc<Self>) -> Result<Rc<Dest>, CastError>;
+}
+
+/// A blanket implementation of `CastRc` for traits extending `CastFrom`.
+impl<CastFromSelf: ?Sized + CastFrom> CastRc for CastFromSelf
+{
+ fn cast<Dest: ?Sized + 'static>(self: Rc<Self>) -> Result<Rc<Dest>, CastError>
+ {
+ let caster =
+ get_caster::<Dest>((*self).type_id()).map_err(CastError::GetCasterFailed)?;
+
+ (caster.cast_rc)(self.rc_any()).map_err(|err| CastError::CastFailed {
+ source: err,
+ from: type_name::<Self>(),
+ to: type_name::<Dest>(),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::any::Any;
+ use std::fmt::{Debug, Display};
+
+ use super::*;
+ use crate::test_utils::subjects;
+
+ #[test]
+ fn can_cast_rc()
+ {
+ let concrete_ninja = Rc::new(subjects::Ninja);
+
+ let abstract_ninja: Rc<dyn subjects::INinja> = concrete_ninja;
+
+ let debug_ninja_result = abstract_ninja.cast::<dyn Debug>();
+
+ assert!(debug_ninja_result.is_ok());
+ }
+
+ #[test]
+ fn cannot_cast_rc_wrong()
+ {
+ let concrete_ninja = Rc::new(subjects::Ninja);
+
+ let abstract_ninja: Rc<dyn subjects::INinja> = concrete_ninja;
+
+ let display_ninja_result = abstract_ninja.cast::<dyn Display>();
+
+ assert!(matches!(
+ display_ninja_result,
+ Err(CastError::GetCasterFailed(_))
+ ));
+ }
+
+ #[test]
+ fn can_cast_rc_from_any()
+ {
+ let concrete_ninja = Rc::new(subjects::Ninja);
+
+ let any_ninja: Rc<dyn Any> = concrete_ninja;
+
+ let debug_ninja_result = any_ninja.cast::<dyn Debug>();
+
+ assert!(debug_ninja_result.is_ok());
+ }
+}
diff --git a/src/private/castable_factory/blocking.rs b/src/private/castable_factory/blocking.rs
new file mode 100644
index 0000000..abc3e26
--- /dev/null
+++ b/src/private/castable_factory/blocking.rs
@@ -0,0 +1,153 @@
+use std::any::type_name;
+use std::fmt::Debug;
+use std::marker::Tuple;
+
+use crate::private::any_factory::AnyFactory;
+use crate::private::factory::IFactory;
+use crate::ptr::TransientPtr;
+
+pub struct CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ func: &'static dyn Fn<Args, Output = TransientPtr<ReturnInterface>>,
+}
+
+impl<Args, ReturnInterface> CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ pub fn new(
+ func: &'static dyn Fn<Args, Output = TransientPtr<ReturnInterface>>,
+ ) -> Self
+ {
+ Self { func }
+ }
+}
+
+impl<Args, ReturnInterface> IFactory<Args, ReturnInterface>
+ for CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+}
+
+impl<Args, ReturnInterface> Fn<Args> for CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ extern "rust-call" fn call(&self, args: Args) -> Self::Output
+ {
+ self.func.call(args)
+ }
+}
+
+impl<Args, ReturnInterface> FnMut<Args> for CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output
+ {
+ self.call(args)
+ }
+}
+
+impl<Args, ReturnInterface> FnOnce<Args> for CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ type Output = TransientPtr<ReturnInterface>;
+
+ extern "rust-call" fn call_once(self, args: Args) -> Self::Output
+ {
+ self.call(args)
+ }
+}
+
+impl<Args, ReturnInterface> AnyFactory for CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+}
+
+impl<Args, ReturnInterface> Debug for CastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ #[cfg(not(tarpaulin_include))]
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ let mut args = type_name::<Args>();
+
+ if args.len() < 2 {
+ return Err(std::fmt::Error::default());
+ }
+
+ args = args
+ .get(1..args.len() - 1)
+ .map_or_else(|| Err(std::fmt::Error::default()), Ok)?;
+
+ if args.ends_with(',') {
+ args = args
+ .get(..args.len() - 1)
+ .map_or_else(|| Err(std::fmt::Error), Ok)?;
+ }
+
+ let ret = type_name::<TransientPtr<ReturnInterface>>();
+
+ formatter.write_fmt(format_args!("CastableFactory ({}) -> {}", args, ret))
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+
+ #[derive(Debug, PartialEq, Eq)]
+ struct Bacon
+ {
+ heal_amount: u32,
+ }
+
+ #[test]
+ fn can_call()
+ {
+ let castable_factory =
+ CastableFactory::new(&|heal_amount| TransientPtr::new(Bacon { heal_amount }));
+
+ let output = castable_factory.call((27,));
+
+ assert_eq!(output, TransientPtr::new(Bacon { heal_amount: 27 }));
+ }
+
+ #[test]
+ fn can_call_mut()
+ {
+ let mut castable_factory =
+ CastableFactory::new(&|heal_amount| TransientPtr::new(Bacon { heal_amount }));
+
+ let output = castable_factory.call_mut((103,));
+
+ assert_eq!(output, TransientPtr::new(Bacon { heal_amount: 103 }));
+ }
+
+ #[test]
+ fn can_call_once()
+ {
+ let castable_factory =
+ CastableFactory::new(&|heal_amount| TransientPtr::new(Bacon { heal_amount }));
+
+ let output = castable_factory.call_once((19,));
+
+ assert_eq!(output, TransientPtr::new(Bacon { heal_amount: 19 }));
+ }
+}
diff --git a/src/private/castable_factory/mod.rs b/src/private/castable_factory/mod.rs
new file mode 100644
index 0000000..e81b842
--- /dev/null
+++ b/src/private/castable_factory/mod.rs
@@ -0,0 +1,4 @@
+pub mod blocking;
+
+#[cfg(feature = "async")]
+pub mod threadsafe;
diff --git a/src/private/castable_factory/threadsafe.rs b/src/private/castable_factory/threadsafe.rs
new file mode 100644
index 0000000..3d2b653
--- /dev/null
+++ b/src/private/castable_factory/threadsafe.rs
@@ -0,0 +1,172 @@
+use std::any::type_name;
+use std::fmt::Debug;
+use std::marker::Tuple;
+
+use crate::private::any_factory::{AnyFactory, AnyThreadsafeFactory};
+use crate::private::factory::IThreadsafeFactory;
+use crate::ptr::TransientPtr;
+
+pub struct ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ func: &'static (dyn Fn<Args, Output = TransientPtr<ReturnInterface>> + Send + Sync),
+}
+
+impl<Args, ReturnInterface> ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ pub fn new(
+ func: &'static (dyn Fn<Args, Output = TransientPtr<ReturnInterface>>
+ + Send
+ + Sync),
+ ) -> Self
+ {
+ Self { func }
+ }
+}
+
+impl<Args, ReturnInterface> IThreadsafeFactory<Args, ReturnInterface>
+ for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+}
+
+impl<Args, ReturnInterface> Fn<Args> for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ extern "rust-call" fn call(&self, args: Args) -> Self::Output
+ {
+ self.func.call(args)
+ }
+}
+
+impl<Args, ReturnInterface> FnMut<Args>
+ for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output
+ {
+ self.call(args)
+ }
+}
+
+impl<Args, ReturnInterface> FnOnce<Args>
+ for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ type Output = TransientPtr<ReturnInterface>;
+
+ extern "rust-call" fn call_once(self, args: Args) -> Self::Output
+ {
+ self.call(args)
+ }
+}
+
+impl<Args, ReturnInterface> AnyFactory
+ for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+}
+
+impl<Args, ReturnInterface> AnyThreadsafeFactory
+ for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+}
+
+impl<Args, ReturnInterface> Debug for ThreadsafeCastableFactory<Args, ReturnInterface>
+where
+ Args: Tuple + 'static,
+ ReturnInterface: 'static + ?Sized,
+{
+ #[cfg(not(tarpaulin_include))]
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ let mut args = type_name::<Args>();
+
+ if args.len() < 2 {
+ return Err(std::fmt::Error::default());
+ }
+
+ args = args
+ .get(1..args.len() - 1)
+ .map_or_else(|| Err(std::fmt::Error::default()), Ok)?;
+
+ if args.ends_with(',') {
+ args = args
+ .get(..args.len() - 1)
+ .map_or_else(|| Err(std::fmt::Error), Ok)?;
+ }
+
+ let ret = type_name::<TransientPtr<ReturnInterface>>();
+
+ formatter.write_fmt(format_args!(
+ "ThreadsafeCastableFactory ({}) -> {}",
+ args, ret
+ ))
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+
+ #[derive(Debug, PartialEq, Eq)]
+ struct Bacon
+ {
+ heal_amount: u32,
+ }
+
+ #[test]
+ fn can_call()
+ {
+ let castable_factory = ThreadsafeCastableFactory::new(&|heal_amount| {
+ TransientPtr::new(Bacon { heal_amount })
+ });
+
+ let output = castable_factory.call((27,));
+
+ assert_eq!(output, TransientPtr::new(Bacon { heal_amount: 27 }));
+ }
+
+ #[test]
+ fn can_call_mut()
+ {
+ let mut castable_factory = ThreadsafeCastableFactory::new(&|heal_amount| {
+ TransientPtr::new(Bacon { heal_amount })
+ });
+
+ let output = castable_factory.call_mut((1092,));
+
+ assert_eq!(output, TransientPtr::new(Bacon { heal_amount: 1092 }));
+ }
+
+ #[test]
+ fn can_call_once()
+ {
+ let castable_factory = ThreadsafeCastableFactory::new(&|heal_amount| {
+ TransientPtr::new(Bacon { heal_amount })
+ });
+
+ let output = castable_factory.call_once((547,));
+
+ assert_eq!(output, TransientPtr::new(Bacon { heal_amount: 547 }));
+ }
+}
diff --git a/src/private/factory.rs b/src/private/factory.rs
new file mode 100644
index 0000000..8b8354d
--- /dev/null
+++ b/src/private/factory.rs
@@ -0,0 +1,23 @@
+use std::marker::Tuple;
+
+use crate::private::cast::CastFrom;
+use crate::ptr::TransientPtr;
+
+/// Interface for a factory.
+pub trait IFactory<Args, ReturnInterface>:
+ Fn<Args, Output = TransientPtr<ReturnInterface>> + CastFrom
+where
+ Args: Tuple,
+ ReturnInterface: 'static + ?Sized,
+{
+}
+
+/// Interface for a threadsafe factory.
+#[cfg(feature = "async")]
+pub trait IThreadsafeFactory<Args, ReturnInterface>:
+ Fn<Args, Output = TransientPtr<ReturnInterface>> + crate::private::cast::CastFromSync
+where
+ Args: Tuple,
+ ReturnInterface: 'static + ?Sized,
+{
+}
diff --git a/src/private/mod.rs b/src/private/mod.rs
new file mode 100644
index 0000000..8b20333
--- /dev/null
+++ b/src/private/mod.rs
@@ -0,0 +1,15 @@
+//! This module contains items that's not in the public API but is used by the
+//! library user with the expansions of the macros in the syrette_macros crate.
+
+pub mod cast;
+
+pub extern crate linkme;
+
+#[cfg(feature = "factory")]
+pub mod any_factory;
+
+#[cfg(feature = "factory")]
+pub mod factory;
+
+#[cfg(feature = "factory")]
+pub mod castable_factory;