bevy_reflect: Store functions as DynamicClosure<'static> in FunctionRegistry (#14704)

# Objective

#14098 added the `FunctionRegistry` for registering functions such that
they can be retrieved by name and used dynamically. One thing we chose
to leave out in that initial PR is support for closures.

Why support closures? Mainly, we don't want to prohibit users from
injecting environmental data into their registered functions. This
allows these functions to not leak their internals to the public API.

For example, let's say we're writing a library crate that allows users
to register callbacks for certain actions. We want to perform some
actions before invoking the user's callback so we can't just call it
directly. We need a closure for this:

```rust
registry.register("my_lib::onclick", move |event: ClickEvent| {
    // ...other work...

    user_onclick.call(event); // <-- Captured variable
});
```

We could have made our callback take a reference to the user's callback.
This would remove the need for the closure, but it would change our
desired API to place the burden of fetching the correct callback on the
caller.

## Solution

Modify the `FunctionRegistry` to store registered functions as
`DynamicClosure<'static>` instead of `DynamicFunction` (now using
`IntoClosure` instead of `IntoFunction`).

Due to limitations in Rust and how function reflection works,
`DynamicClosure<'static>` is functionally equivalent to
`DynamicFunction`. And a normal function is considered a subset of
closures (it's a closure that doesn't capture anything), so there
shouldn't be any difference in usage: all functions that satisfy
`IntoFunction` should satisfy `IntoClosure`.

This means that the registration API introduced in #14098 should require
little-to-no changes on anyone following `main`.

### Closures vs Functions

One consideration here is whether we should keep closures and functions
separate.

This PR unifies them into `DynamicClosure<'static>`, but we can consider
splitting them up. The reasons we might want to do so are:

- Simplifies mental model and terminology (users don't have to
understand that functions turn into closures)
- If Rust ever improves its function model, we may be able to add
additional guarantees to `DynamicFunction` that make it useful to
separate the two
- Adding support for generic functions may be less confusing for users
since closures in Rust technically can't be generic

The reasons behind this PR's unification approach are:

- Reduces the number of methods needed on `FunctionRegistry`
- Reduces the number of lookups a user may have to perform (i.e.
"`get_function` or else `get_closure`")
- Establishes `DynamicClosure<'static>` as the de facto dynamic callable
(similar to how most APIs in Rust code tend to prefer `impl Fn() ->
String` over `fn() -> String`)

I'd love to hear feedback on this matter, and whether we should continue
with this PR's approach or switch to a split model.

## Testing

You can test locally by running:

```
cargo test --package bevy_reflect
```

---

## Showcase

Closures can now be registered into the `FunctionRegistry`:

```rust
let punct = String::from("!!!");

registry.register_with_name("my_crate::punctuate", move |text: String| {
  format!("{}{}", text, punct)
});
```
This commit is contained in:
Gino Valente 2024-08-16 17:20:47 -07:00 committed by GitHub
parent da529ff09e
commit 423285cf1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 212 additions and 95 deletions

View File

@ -605,16 +605,16 @@ impl App {
/// Registers the given function into the [`AppFunctionRegistry`] resource. /// Registers the given function into the [`AppFunctionRegistry`] resource.
/// ///
/// The given function will internally be stored as a [`DynamicFunction`] /// The given function will internally be stored as a [`DynamicClosure`]
/// and mapped according to its [name]. /// and mapped according to its [name].
/// ///
/// Because the function must have a name, /// Because the function must have a name,
/// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) must instead /// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) and closures must instead
/// be registered using [`register_function_with_name`] or converted to a [`DynamicFunction`] /// be registered using [`register_function_with_name`] or converted to a [`DynamicClosure`]
/// and named using [`DynamicFunction::with_name`]. /// and named using [`DynamicClosure::with_name`].
/// Failure to do so will result in an error being returned. /// Failure to do so will result in a panic.
/// ///
/// Only functions that implement [`IntoFunction`] may be registered via this method. /// Only types that implement [`IntoClosure`] may be registered via this method.
/// ///
/// See [`FunctionRegistry::register`] for more information. /// See [`FunctionRegistry::register`] for more information.
/// ///
@ -650,7 +650,7 @@ impl App {
/// .register_function(add); /// .register_function(add);
/// ``` /// ```
/// ///
/// Anonymous functions should be registered using [`register_function_with_name`] or given a name using [`DynamicFunction::with_name`]. /// Anonymous functions and closures should be registered using [`register_function_with_name`] or given a name using [`DynamicClosure::with_name`].
/// ///
/// ```should_panic /// ```should_panic
/// use bevy_app::App; /// use bevy_app::App;
@ -660,21 +660,21 @@ impl App {
/// ``` /// ```
/// ///
/// [`register_function_with_name`]: Self::register_function_with_name /// [`register_function_with_name`]: Self::register_function_with_name
/// [`DynamicFunction`]: bevy_reflect::func::DynamicFunction /// [`DynamicClosure`]: bevy_reflect::func::DynamicClosure
/// [name]: bevy_reflect::func::FunctionInfo::name /// [name]: bevy_reflect::func::FunctionInfo::name
/// [`DynamicFunction::with_name`]: bevy_reflect::func::DynamicFunction::with_name /// [`DynamicClosure::with_name`]: bevy_reflect::func::DynamicClosure::with_name
/// [`IntoFunction`]: bevy_reflect::func::IntoFunction /// [`IntoClosure`]: bevy_reflect::func::IntoClosure
/// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register /// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register
#[cfg(feature = "reflect_functions")] #[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self
where where
F: bevy_reflect::func::IntoFunction<Marker> + 'static, F: bevy_reflect::func::IntoClosure<'static, Marker> + 'static,
{ {
self.main_mut().register_function(function); self.main_mut().register_function(function);
self self
} }
/// Registers the given function into the [`AppFunctionRegistry`] resource using the given name. /// Registers the given function or closure into the [`AppFunctionRegistry`] resource using the given name.
/// ///
/// To avoid conflicts, it's recommended to use a unique name for the function. /// To avoid conflicts, it's recommended to use a unique name for the function.
/// This can be achieved by "namespacing" the function with a unique identifier, /// This can be achieved by "namespacing" the function with a unique identifier,
@ -689,7 +689,7 @@ impl App {
/// For named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) where a custom name is not needed, /// For named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) where a custom name is not needed,
/// it's recommended to use [`register_function`] instead as the generated name is guaranteed to be unique. /// it's recommended to use [`register_function`] instead as the generated name is guaranteed to be unique.
/// ///
/// Only functions that implement [`IntoFunction`] may be registered via this method. /// Only types that implement [`IntoClosure`] may be registered via this method.
/// ///
/// See [`FunctionRegistry::register_with_name`] for more information. /// See [`FunctionRegistry::register_with_name`] for more information.
/// ///
@ -718,7 +718,7 @@ impl App {
/// // Registering an existing function with a custom name /// // Registering an existing function with a custom name
/// .register_function_with_name("my_crate::mul", mul) /// .register_function_with_name("my_crate::mul", mul)
/// // Be careful not to register anonymous functions with their type name. /// // Be careful not to register anonymous functions with their type name.
/// // This code works but registers the function with the non-unique name of `fn(i32, i32) -> i32` /// // This code works but registers the function with a non-unique name like `foo::bar::{{closure}}`
/// .register_function_with_name(std::any::type_name_of_val(&div), div); /// .register_function_with_name(std::any::type_name_of_val(&div), div);
/// ``` /// ```
/// ///
@ -738,7 +738,7 @@ impl App {
/// ///
/// [type name]: std::any::type_name /// [type name]: std::any::type_name
/// [`register_function`]: Self::register_function /// [`register_function`]: Self::register_function
/// [`IntoFunction`]: bevy_reflect::func::IntoFunction /// [`IntoClosure`]: bevy_reflect::func::IntoClosure
/// [`FunctionRegistry::register_with_name`]: bevy_reflect::func::FunctionRegistry::register_with_name /// [`FunctionRegistry::register_with_name`]: bevy_reflect::func::FunctionRegistry::register_with_name
#[cfg(feature = "reflect_functions")] #[cfg(feature = "reflect_functions")]
pub fn register_function_with_name<F, Marker>( pub fn register_function_with_name<F, Marker>(
@ -747,7 +747,7 @@ impl App {
function: F, function: F,
) -> &mut Self ) -> &mut Self
where where
F: bevy_reflect::func::IntoFunction<Marker> + 'static, F: bevy_reflect::func::IntoClosure<'static, Marker> + 'static,
{ {
self.main_mut().register_function_with_name(name, function); self.main_mut().register_function_with_name(name, function);
self self

View File

@ -413,7 +413,7 @@ impl SubApp {
#[cfg(feature = "reflect_functions")] #[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self
where where
F: bevy_reflect::func::IntoFunction<Marker> + 'static, F: bevy_reflect::func::IntoClosure<'static, Marker> + 'static,
{ {
let registry = self.world.resource_mut::<AppFunctionRegistry>(); let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register(function).unwrap(); registry.write().register(function).unwrap();
@ -428,7 +428,7 @@ impl SubApp {
function: F, function: F,
) -> &mut Self ) -> &mut Self
where where
F: bevy_reflect::func::IntoFunction<Marker> + 'static, F: bevy_reflect::func::IntoClosure<'static, Marker> + 'static,
{ {
let registry = self.world.resource_mut::<AppFunctionRegistry>(); let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register_with_name(name, function).unwrap(); registry.write().register_with_name(name, function).unwrap();

View File

@ -1,9 +1,11 @@
use alloc::borrow::Cow;
use core::fmt::{Debug, Formatter};
use crate::func::args::{ArgInfo, ArgList}; use crate::func::args::{ArgInfo, ArgList};
use crate::func::info::FunctionInfo; use crate::func::info::FunctionInfo;
use crate::func::{FunctionResult, IntoClosure, ReturnInfo}; use crate::func::{
DynamicClosureMut, DynamicFunction, FunctionResult, IntoClosure, IntoClosureMut, ReturnInfo,
};
use alloc::borrow::Cow;
use core::fmt::{Debug, Formatter};
use std::sync::Arc;
/// A dynamic representation of a Rust closure. /// A dynamic representation of a Rust closure.
/// ///
@ -42,12 +44,9 @@ use crate::func::{FunctionResult, IntoClosure, ReturnInfo};
/// // Check the result: /// // Check the result:
/// assert_eq!(value.try_take::<String>().unwrap(), "Hello, world!!!"); /// assert_eq!(value.try_take::<String>().unwrap(), "Hello, world!!!");
/// ``` /// ```
///
/// [`DynamicClosureMut`]: crate::func::closures::DynamicClosureMut
/// [`DynamicFunction`]: crate::func::DynamicFunction
pub struct DynamicClosure<'env> { pub struct DynamicClosure<'env> {
info: FunctionInfo, pub(super) info: FunctionInfo,
func: Box<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'env>, pub(super) func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>,
} }
impl<'env> DynamicClosure<'env> { impl<'env> DynamicClosure<'env> {
@ -57,13 +56,13 @@ impl<'env> DynamicClosure<'env> {
/// ///
/// It's important that the closure signature matches the provided [`FunctionInfo`]. /// It's important that the closure signature matches the provided [`FunctionInfo`].
/// This info may be used by consumers of the function for validation and debugging. /// This info may be used by consumers of the function for validation and debugging.
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'env>( pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>(
func: F, func: F,
info: FunctionInfo, info: FunctionInfo,
) -> Self { ) -> Self {
Self { Self {
info, info,
func: Box::new(func), func: Arc::new(func),
} }
} }
@ -160,6 +159,25 @@ impl<'env> Debug for DynamicClosure<'env> {
} }
} }
impl<'env> Clone for DynamicClosure<'env> {
fn clone(&self) -> Self {
Self {
info: self.info.clone(),
func: Arc::clone(&self.func),
}
}
}
impl From<DynamicFunction> for DynamicClosure<'static> {
#[inline]
fn from(func: DynamicFunction) -> Self {
Self {
info: func.info,
func: func.func,
}
}
}
impl<'env> IntoClosure<'env, ()> for DynamicClosure<'env> { impl<'env> IntoClosure<'env, ()> for DynamicClosure<'env> {
#[inline] #[inline]
fn into_closure(self) -> DynamicClosure<'env> { fn into_closure(self) -> DynamicClosure<'env> {
@ -167,6 +185,13 @@ impl<'env> IntoClosure<'env, ()> for DynamicClosure<'env> {
} }
} }
impl<'env> IntoClosureMut<'env, ()> for DynamicClosure<'env> {
#[inline]
fn into_closure_mut(self) -> DynamicClosureMut<'env> {
DynamicClosureMut::from(self)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -190,4 +215,26 @@ mod tests {
let closure: DynamicClosure = make_closure(|a: i32, b: i32| a + b + c); let closure: DynamicClosure = make_closure(|a: i32, b: i32| a + b + c);
let _: DynamicClosure = make_closure(closure); let _: DynamicClosure = make_closure(closure);
} }
#[test]
fn should_clone_dynamic_closure() {
let hello = String::from("Hello");
let greet = |name: &String| -> String { format!("{}, {}!", hello, name) };
let greet = greet.into_closure().with_name("greet");
let clone = greet.clone();
assert_eq!(greet.name().unwrap(), "greet");
assert_eq!(clone.name().unwrap(), "greet");
let clone_value = clone
.call(ArgList::default().push_ref(&String::from("world")))
.unwrap()
.unwrap_owned()
.try_take::<String>()
.unwrap();
assert_eq!(clone_value, "Hello, world!");
}
} }

View File

@ -3,7 +3,7 @@ use core::fmt::{Debug, Formatter};
use crate::func::args::{ArgInfo, ArgList}; use crate::func::args::{ArgInfo, ArgList};
use crate::func::info::FunctionInfo; use crate::func::info::FunctionInfo;
use crate::func::{FunctionResult, IntoClosureMut, ReturnInfo}; use crate::func::{DynamicClosure, DynamicFunction, FunctionResult, IntoClosureMut, ReturnInfo};
/// A dynamic representation of a Rust closure. /// A dynamic representation of a Rust closure.
/// ///
@ -51,9 +51,6 @@ use crate::func::{FunctionResult, IntoClosureMut, ReturnInfo};
/// drop(func); /// drop(func);
/// assert_eq!(list, vec![1, -2, 3]); /// assert_eq!(list, vec![1, -2, 3]);
/// ``` /// ```
///
/// [`DynamicClosure`]: crate::func::closures::DynamicClosure
/// [`DynamicFunction`]: crate::func::DynamicFunction
pub struct DynamicClosureMut<'env> { pub struct DynamicClosureMut<'env> {
info: FunctionInfo, info: FunctionInfo,
func: Box<dyn for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>, func: Box<dyn for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>,
@ -202,6 +199,26 @@ impl<'env> Debug for DynamicClosureMut<'env> {
} }
} }
impl From<DynamicFunction> for DynamicClosureMut<'static> {
#[inline]
fn from(func: DynamicFunction) -> Self {
Self {
info: func.info,
func: Box::new(move |args| (func.func)(args)),
}
}
}
impl<'env> From<DynamicClosure<'env>> for DynamicClosureMut<'env> {
#[inline]
fn from(closure: DynamicClosure<'env>) -> Self {
Self {
info: closure.info,
func: Box::new(move |args| (closure.func)(args)),
}
}
}
impl<'env> IntoClosureMut<'env, ()> for DynamicClosureMut<'env> { impl<'env> IntoClosureMut<'env, ()> for DynamicClosureMut<'env> {
#[inline] #[inline]
fn into_closure_mut(self) -> DynamicClosureMut<'env> { fn into_closure_mut(self) -> DynamicClosureMut<'env> {

View File

@ -18,7 +18,7 @@ pub trait IntoClosure<'env, Marker> {
impl<'env, F, Marker1, Marker2> IntoClosure<'env, (Marker1, Marker2)> for F impl<'env, F, Marker1, Marker2> IntoClosure<'env, (Marker1, Marker2)> for F
where where
F: ReflectFn<'env, Marker1> + TypedFunction<Marker2> + 'env, F: ReflectFn<'env, Marker1> + TypedFunction<Marker2> + Send + Sync + 'env,
{ {
fn into_closure(self) -> DynamicClosure<'env> { fn into_closure(self) -> DynamicClosure<'env> {
DynamicClosure::new(move |args| self.reflect_call(args), Self::function_info()) DynamicClosure::new(move |args| self.reflect_call(args), Self::function_info())

View File

@ -4,7 +4,10 @@ use std::sync::Arc;
use crate::func::args::{ArgInfo, ArgList}; use crate::func::args::{ArgInfo, ArgList};
use crate::func::info::FunctionInfo; use crate::func::info::FunctionInfo;
use crate::func::{FunctionResult, IntoFunction, ReturnInfo}; use crate::func::{
DynamicClosure, DynamicClosureMut, FunctionResult, IntoClosure, IntoClosureMut, IntoFunction,
ReturnInfo,
};
/// A dynamic representation of a Rust function. /// A dynamic representation of a Rust function.
/// ///
@ -88,11 +91,10 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
/// assert_eq!(list, vec!["Hello, World!!!"]); /// assert_eq!(list, vec!["Hello, World!!!"]);
/// ``` /// ```
/// ///
/// [`DynamicClosure`]: crate::func::DynamicClosure
/// [module-level documentation]: crate::func /// [module-level documentation]: crate::func
pub struct DynamicFunction { pub struct DynamicFunction {
info: FunctionInfo, pub(super) info: FunctionInfo,
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>, pub(super) func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>,
} }
impl DynamicFunction { impl DynamicFunction {
@ -221,6 +223,20 @@ impl IntoFunction<()> for DynamicFunction {
} }
} }
impl IntoClosure<'_, ()> for DynamicFunction {
#[inline]
fn into_closure(self) -> DynamicClosure<'static> {
DynamicClosure::from(self)
}
}
impl IntoClosureMut<'_, ()> for DynamicFunction {
#[inline]
fn into_closure_mut(self) -> DynamicClosureMut<'static> {
DynamicClosureMut::from(self)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -4,35 +4,40 @@ use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
use bevy_utils::HashMap; use bevy_utils::HashMap;
use crate::func::{DynamicFunction, FunctionRegistrationError, IntoFunction}; use crate::func::{DynamicClosure, FunctionRegistrationError, IntoClosure};
/// A registry of [reflected functions]. /// A registry of [reflected functions].
/// ///
/// This is the function-equivalent to the [`TypeRegistry`]. /// This is the function-equivalent to the [`TypeRegistry`].
/// ///
/// All functions and closures are stored as `'static` closures via [`DynamicClosure<'static>`].
/// [`DynamicClosure`] is used instead of [`DynamicFunction`] as it can represent both functions and closures,
/// allowing registered functions to contain captured environment data.
///
/// [reflected functions]: crate::func /// [reflected functions]: crate::func
/// [`TypeRegistry`]: crate::TypeRegistry /// [`TypeRegistry`]: crate::TypeRegistry
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Default)] #[derive(Default)]
pub struct FunctionRegistry { pub struct FunctionRegistry {
/// Maps function [names] to their respective [`DynamicFunctions`]. /// Maps function [names] to their respective [`DynamicClosures`].
/// ///
/// [names]: DynamicFunction::name /// [names]: DynamicClosure::name
/// [`DynamicFunctions`]: DynamicFunction /// [`DynamicClosures`]: DynamicClosure
functions: HashMap<Cow<'static, str>, DynamicFunction>, functions: HashMap<Cow<'static, str>, DynamicClosure<'static>>,
} }
impl FunctionRegistry { impl FunctionRegistry {
/// Attempts to register the given function. /// Attempts to register the given function.
/// ///
/// This function accepts both functions that satisfy [`IntoFunction`] /// This function accepts both functions/closures that satisfy [`IntoClosure`]
/// and direct [`DynamicFunction`] instances. /// and direct [`DynamicFunction`]/[`DynamicClosure`] instances.
/// The given function will internally be stored as a [`DynamicFunction`] /// The given function will internally be stored as a [`DynamicClosure<'static>`]
/// and mapped according to its [name]. /// and mapped according to its [name].
/// ///
/// Because the function must have a name, /// Because the function must have a name,
/// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) must instead /// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) and closures must instead
/// be registered using [`register_with_name`] or converted to a [`DynamicFunction`] /// be registered using [`register_with_name`] or converted to a [`DynamicClosure`]
/// and named using [`DynamicFunction::with_name`]. /// and named using [`DynamicClosure::with_name`].
/// Failure to do so will result in an error being returned. /// Failure to do so will result in an error being returned.
/// ///
/// If a registered function with the same name already exists, /// If a registered function with the same name already exists,
@ -58,7 +63,7 @@ impl FunctionRegistry {
/// Functions cannot be registered more than once. /// Functions cannot be registered more than once.
/// ///
/// ``` /// ```
/// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction}; /// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoClosure};
/// fn add(a: i32, b: i32) -> i32 { /// fn add(a: i32, b: i32) -> i32 {
/// a + b /// a + b
/// } /// }
@ -71,14 +76,14 @@ impl FunctionRegistry {
/// ///
/// // Note that this simply relies on the name of the function to determine uniqueness. /// // Note that this simply relies on the name of the function to determine uniqueness.
/// // You can rename the function to register a separate instance of it. /// // You can rename the function to register a separate instance of it.
/// let result = registry.register(add.into_function().with_name("add2")); /// let result = registry.register(add.into_closure().with_name("add2"));
/// assert!(result.is_ok()); /// assert!(result.is_ok());
/// ``` /// ```
/// ///
/// Anonymous functions should be registered using [`register_with_name`] or given a name using [`DynamicFunction::with_name`]. /// Anonymous functions and closures should be registered using [`register_with_name`] or given a name using [`DynamicClosure::with_name`].
/// ///
/// ``` /// ```
/// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction}; /// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoClosure};
/// ///
/// let anonymous = || -> i32 { 123 }; /// let anonymous = || -> i32 { 123 };
/// ///
@ -90,11 +95,12 @@ impl FunctionRegistry {
/// let result = registry.register_with_name("my_crate::add", |a: i32, b: i32| a + b); /// let result = registry.register_with_name("my_crate::add", |a: i32, b: i32| a + b);
/// assert!(result.is_ok()); /// assert!(result.is_ok());
/// ///
/// let result = registry.register((|a: i32, b: i32| a * b).into_function().with_name("my_crate::mul")); /// let result = registry.register((|a: i32, b: i32| a * b).into_closure().with_name("my_crate::mul"));
/// assert!(result.is_ok()); /// assert!(result.is_ok());
/// ``` /// ```
/// ///
/// [name]: DynamicFunction::name /// [`DynamicFunction`]: crate::func::DynamicFunction
/// [name]: DynamicClosure::name
/// [`register_with_name`]: Self::register_with_name /// [`register_with_name`]: Self::register_with_name
/// [`overwrite_registration`]: Self::overwrite_registration /// [`overwrite_registration`]: Self::overwrite_registration
pub fn register<F, Marker>( pub fn register<F, Marker>(
@ -102,15 +108,15 @@ impl FunctionRegistry {
function: F, function: F,
) -> Result<&mut Self, FunctionRegistrationError> ) -> Result<&mut Self, FunctionRegistrationError>
where where
F: IntoFunction<Marker> + 'static, F: IntoClosure<'static, Marker> + 'static,
{ {
let function = function.into_function(); let function = function.into_closure();
let name = function let name = function
.name() .name()
.ok_or(FunctionRegistrationError::MissingName)? .ok_or(FunctionRegistrationError::MissingName)?
.clone(); .clone();
self.functions self.functions
.try_insert(name, function) .try_insert(name, function.into_closure())
.map_err(|err| FunctionRegistrationError::DuplicateName(err.entry.key().clone()))?; .map_err(|err| FunctionRegistrationError::DuplicateName(err.entry.key().clone()))?;
Ok(self) Ok(self)
@ -118,9 +124,9 @@ impl FunctionRegistry {
/// Attempts to register the given function with the given name. /// Attempts to register the given function with the given name.
/// ///
/// This function accepts both functions that satisfy [`IntoFunction`] /// This function accepts both functions/closures that satisfy [`IntoClosure`]
/// and direct [`DynamicFunction`] instances. /// and direct [`DynamicFunction`]/[`DynamicClosure`] instances.
/// The given function will internally be stored as a [`DynamicFunction`] /// The given function will internally be stored as a [`DynamicClosure<'static>`]
/// with its [name] set to the given name. /// with its [name] set to the given name.
/// ///
/// For named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) where a custom name is not needed, /// For named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) where a custom name is not needed,
@ -141,7 +147,7 @@ impl FunctionRegistry {
/// Another approach could be to use the [type name] of the function, /// Another approach could be to use the [type name] of the function,
/// however, it should be noted that anonymous functions do _not_ have unique type names. /// however, it should be noted that anonymous functions do _not_ have unique type names.
/// ///
/// This method is a convenience around calling [`IntoFunction::into_function`] and [`DynamicFunction::with_name`] /// This method is a convenience around calling [`IntoClosure::into_closure`] and [`DynamicClosure::with_name`]
/// on the function and inserting it into the registry using the [`register`] method. /// on the function and inserting it into the registry using the [`register`] method.
/// ///
/// # Examples /// # Examples
@ -167,7 +173,7 @@ impl FunctionRegistry {
/// .register_with_name("my_crate::mul", mul)?; /// .register_with_name("my_crate::mul", mul)?;
/// ///
/// // Be careful not to register anonymous functions with their type name. /// // Be careful not to register anonymous functions with their type name.
/// // This code works but registers the function with the non-unique name of `fn(i32, i32) -> i32` /// // This code works but registers the function with a non-unique name like `foo::bar::{{closure}}`
/// registry.register_with_name(std::any::type_name_of_val(&div), div)?; /// registry.register_with_name(std::any::type_name_of_val(&div), div)?;
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -187,7 +193,8 @@ impl FunctionRegistry {
/// registry.register_with_name("my_function", two).unwrap(); /// registry.register_with_name("my_function", two).unwrap();
/// ``` /// ```
/// ///
/// [name]: DynamicFunction::name /// [`DynamicFunction`]: crate::func::DynamicFunction
/// [name]: DynamicClosure::name
/// [`register`]: Self::register /// [`register`]: Self::register
/// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name /// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name
/// [type name]: std::any::type_name /// [type name]: std::any::type_name
@ -197,23 +204,23 @@ impl FunctionRegistry {
function: F, function: F,
) -> Result<&mut Self, FunctionRegistrationError> ) -> Result<&mut Self, FunctionRegistrationError>
where where
F: IntoFunction<Marker> + 'static, F: IntoClosure<'static, Marker> + 'static,
{ {
let function = function.into_function().with_name(name); let function = function.into_closure().with_name(name);
self.register(function) self.register(function)
} }
/// Registers the given function, overwriting any existing registration. /// Registers the given function, overwriting any existing registration.
/// ///
/// This function accepts both functions that satisfy [`IntoFunction`] /// This function accepts both functions/closures that satisfy [`IntoClosure`]
/// and direct [`DynamicFunction`] instances. /// and direct [`DynamicFunction`]/[`DynamicClosure`] instances.
/// The given function will internally be stored as a [`DynamicFunction`] /// The given function will internally be stored as a [`DynamicClosure<'static>`]
/// and mapped according to its [name]. /// and mapped according to its [name].
/// ///
/// Because the function must have a name, /// Because the function must have a name,
/// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) must instead /// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) and closures must instead
/// be registered using [`overwrite_registration_with_name`] or converted to a [`DynamicFunction`] /// be registered using [`overwrite_registration_with_name`] or converted to a [`DynamicClosure`]
/// and named using [`DynamicFunction::with_name`]. /// and named using [`DynamicClosure::with_name`].
/// Failure to do so will result in an error being returned. /// Failure to do so will result in an error being returned.
/// ///
/// To avoid overwriting existing registrations, /// To avoid overwriting existing registrations,
@ -221,17 +228,18 @@ impl FunctionRegistry {
/// ///
/// Returns the previous function with the same name, if any. /// Returns the previous function with the same name, if any.
/// ///
/// [name]: DynamicFunction::name /// [`DynamicFunction`]: crate::func::DynamicFunction
/// [name]: DynamicClosure::name
/// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name /// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name
/// [`register`]: Self::register /// [`register`]: Self::register
pub fn overwrite_registration<F, Marker>( pub fn overwrite_registration<F, Marker>(
&mut self, &mut self,
function: F, function: F,
) -> Result<Option<DynamicFunction>, FunctionRegistrationError> ) -> Result<Option<DynamicClosure<'static>>, FunctionRegistrationError>
where where
F: IntoFunction<Marker> + 'static, F: IntoClosure<'static, Marker> + 'static,
{ {
let function = function.into_function(); let function = function.into_closure();
let name = function let name = function
.name() .name()
.ok_or(FunctionRegistrationError::MissingName)? .ok_or(FunctionRegistrationError::MissingName)?
@ -242,32 +250,33 @@ impl FunctionRegistry {
/// Registers the given function, overwriting any existing registration. /// Registers the given function, overwriting any existing registration.
/// ///
/// This function accepts both functions that satisfy [`IntoFunction`] /// This function accepts both functions/closures that satisfy [`IntoClosure`]
/// and direct [`DynamicFunction`] instances. /// and direct [`DynamicFunction`]/[`DynamicClosure`] instances.
/// The given function will internally be stored as a [`DynamicFunction`] /// The given function will internally be stored as a [`DynamicClosure<'static>`]
/// with its [name] set to the given name. /// with its [name] set to the given name.
/// ///
/// Functions are mapped according to their name. /// Functions are mapped according to their name.
/// To avoid overwriting existing registrations, /// To avoid overwriting existing registrations,
/// it's recommended to use the [`register_with_name`] method instead. /// it's recommended to use the [`register_with_name`] method instead.
/// ///
/// This method is a convenience around calling [`IntoFunction::into_function`] and [`DynamicFunction::with_name`] /// This method is a convenience around calling [`IntoClosure::into_closure`] and [`DynamicClosure::with_name`]
/// on the function and inserting it into the registry using the [`overwrite_registration`] method. /// on the function and inserting it into the registry using the [`overwrite_registration`] method.
/// ///
/// Returns the previous function with the same name, if any. /// Returns the previous function with the same name, if any.
/// ///
/// [name]: DynamicFunction::name /// [`DynamicFunction`]: crate::func::DynamicFunction
/// [name]: DynamicClosure::name
/// [`register_with_name`]: Self::register_with_name /// [`register_with_name`]: Self::register_with_name
/// [`overwrite_registration`]: Self::overwrite_registration /// [`overwrite_registration`]: Self::overwrite_registration
pub fn overwrite_registration_with_name<F, Marker>( pub fn overwrite_registration_with_name<F, Marker>(
&mut self, &mut self,
name: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>,
function: F, function: F,
) -> Option<DynamicFunction> ) -> Option<DynamicClosure<'static>>
where where
F: IntoFunction<Marker> + 'static, F: IntoClosure<'static, Marker> + 'static,
{ {
let function = function.into_function().with_name(name); let function = function.into_closure().with_name(name);
match self.overwrite_registration(function) { match self.overwrite_registration(function) {
Ok(existing) => existing, Ok(existing) => existing,
Err(FunctionRegistrationError::MissingName) => { Err(FunctionRegistrationError::MissingName) => {
@ -279,31 +288,31 @@ impl FunctionRegistry {
} }
} }
/// Get a reference to a registered function by [name]. /// Get a reference to a registered function or closure by [name].
/// ///
/// [name]: DynamicFunction::name /// [name]: DynamicClosure::name
pub fn get(&self, name: &str) -> Option<&DynamicFunction> { pub fn get(&self, name: &str) -> Option<&DynamicClosure<'static>> {
self.functions.get(name) self.functions.get(name)
} }
/// Returns `true` if a function with the given [name] is registered. /// Returns `true` if a function or closure with the given [name] is registered.
/// ///
/// [name]: DynamicFunction::name /// [name]: DynamicClosure::name
pub fn contains(&self, name: &str) -> bool { pub fn contains(&self, name: &str) -> bool {
self.functions.contains_key(name) self.functions.contains_key(name)
} }
/// Returns an iterator over all registered functions. /// Returns an iterator over all registered functions/closures.
pub fn iter(&self) -> impl ExactSizeIterator<Item = &DynamicFunction> { pub fn iter(&self) -> impl ExactSizeIterator<Item = &DynamicClosure<'static>> {
self.functions.values() self.functions.values()
} }
/// Returns the number of registered functions. /// Returns the number of registered functions/closures.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.functions.len() self.functions.len()
} }
/// Returns `true` if no functions are registered. /// Returns `true` if no functions or closures are registered.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.functions.is_empty() self.functions.is_empty()
} }
@ -338,7 +347,7 @@ impl FunctionRegistryArc {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::func::ArgList; use crate::func::{ArgList, IntoFunction};
#[test] #[test]
fn should_register_function() { fn should_register_function() {
@ -364,6 +373,19 @@ mod tests {
assert_eq!(value.try_downcast_ref::<i32>(), Some(&123)); assert_eq!(value.try_downcast_ref::<i32>(), Some(&123));
} }
#[test]
fn should_register_closure() {
let value = 123;
let foo = move || -> i32 { value };
let mut registry = FunctionRegistry::default();
registry.register_with_name("foo", foo).unwrap();
let function = registry.get("foo").unwrap();
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
assert_eq!(value.try_downcast_ref::<i32>(), Some(&123));
}
#[test] #[test]
fn should_register_dynamic_function() { fn should_register_dynamic_function() {
fn foo() -> i32 { fn foo() -> i32 {
@ -380,6 +402,21 @@ mod tests {
assert_eq!(value.try_downcast_ref::<i32>(), Some(&123)); assert_eq!(value.try_downcast_ref::<i32>(), Some(&123));
} }
#[test]
fn should_register_dynamic_closure() {
let value = 123;
let foo = move || -> i32 { value };
let function = foo.into_closure().with_name("custom_name");
let mut registry = FunctionRegistry::default();
registry.register(function).unwrap();
let function = registry.get("custom_name").unwrap();
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
assert_eq!(value.try_downcast_ref::<i32>(), Some(&123));
}
#[test] #[test]
fn should_only_register_function_once() { fn should_only_register_function_once() {
fn foo() -> i32 { fn foo() -> i32 {
@ -457,6 +494,6 @@ mod tests {
registry.register_with_name("foo", foo).unwrap(); registry.register_with_name("foo", foo).unwrap();
let debug = format!("{:?}", registry); let debug = format!("{:?}", registry);
assert_eq!(debug, "{DynamicFunction(fn foo() -> i32)}"); assert_eq!(debug, "{DynamicClosure(fn foo() -> i32)}");
} }
} }