Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bevy_reflect: Function reflection #13152

Merged

Conversation

MrGVSV
Copy link
Member

@MrGVSV MrGVSV commented Apr 30, 2024

Objective

We're able to reflect types sooooooo... why not functions?

The goal of this PR is to make functions callable within a dynamic context, where type information is not readily available at compile time.

For example, if we have a function:

fn add(left: i32, right: i32) -> i32 {
  left + right
}

And two Reflect values we've already validated are i32 types:

let left: Box<dyn Reflect> = Box::new(2_i32);
let right: Box<dyn Reflect> = Box::new(2_i32);

We should be able to call add with these values:

// ?????
let result: Box<dyn Reflect> = add.call_dynamic(left, right);

And ideally this wouldn't just work for functions, but methods and closures too!

Right now, users have two options:

  1. Manually parse the reflected data and call the function themselves
  2. Rely on registered type data to handle the conversions for them

For a small function like add, this isn't too bad. But what about for more complex functions? What about for many functions?

At worst, this process is error-prone. At best, it's simply tedious.

And this is assuming we know the function at compile time. What if we want to accept a function dynamically and call it with our own arguments?

It would be much nicer if bevy_reflect could alleviate some of the problems here.

Solution

Introducing function reflection!

This adds a DynamicFunction type to wrap a function dynamically. This can be called with an ArgList, which is a dynamic list of Reflect-containing Arg arguments. It returns a FunctionResult which indicates whether or not the function call succeeded, returning a Reflect-containing Return type if it did succeed.

Many functions can be converted into this DynamicFunction type thanks to the IntoFunction trait.

Taking our previous add example, this might look something like (explicit types added for readability):

fn add(left: i32, right: i32) -> i32 {
  left + right
}

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);

And it also works on closures:

let add = |left: i32, right: i32| left + right;

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);

As well as methods:

#[derive(Reflect)]
struct Foo(i32);

impl Foo {
  fn add(&mut self, value: i32) {
    self.0 += value;
  }
}

let mut foo = Foo(2);

let mut function: DynamicFunction = Foo::add.into_function();
let args: ArgList = ArgList::new().push_mut(&mut foo).push_owned(2_i32);
function.call(args).unwrap();
assert_eq!(foo.0, 4);

Limitations

While this does cover many functions, it is far from a perfect system and has quite a few limitations. Here are a few of the limitations when using IntoFunction:

  1. The lifetime of the return value is only tied to the lifetime of the first argument (useful for methods). This means you can't have a function like (a: i32, b: &i32) -> &i32 without creating the DynamicFunction manually.
  2. Only 15 arguments are currently supported. If the first argument is a (mutable) reference, this number increases to 16.
  3. Manual implementations of Reflect will need to implement the new FromArg, GetOwnership, and IntoReturn traits in order to be used as arguments/return types.

And some limitations of DynamicFunction itself:

  1. All arguments share the same lifetime, or rather, they will shrink to the shortest lifetime.
  2. Closures that capture their environment may need to have their DynamicFunction dropped before accessing those variables again (there is a DynamicFunction::call_once to make this a bit easier)
  3. All arguments and return types must implement Reflect. While not a big surprise coming from bevy_reflect, this implementation could actually still work by swapping Reflect out with Any. Of course, that makes working with the arguments and return values a bit harder.
  4. Generic functions are not supported (unless they have been manually monomorphized)

And general, reflection gotchas:

  1. &str does not implement Reflect. Rather, &'static str implements Reflect (the same is true for &Path and similar types). This means that &'static str is considered an "owned" value for the sake of generating arguments. Additionally, arguments and return types containing &str will assume it's &'static str, which is almost never the desired behavior. In these cases, the only solution (I believe) is to use &String instead.

Followup Work

This PR is the first of two PRs I intend to work on. The second PR will aim to integrate this new function reflection system into the existing reflection traits and TypeInfo. The goal would be to register and call a reflected type's methods dynamically.

I chose not to do that in this PR since the diff is already quite large. I also want the discussion for both PRs to be focused on their own implementation.

Another followup I'd like to do is investigate allowing common container types as a return type, such as Option<&[mut] T> and Result<&[mut] T, E>. This would allow even more functions to opt into this system. I chose to not include it in this one, though, for the same reasoning as previously mentioned.

Alternatives

One alternative I had considered was adding a macro to convert any function into a reflection-based counterpart. The idea would be that a struct that wraps the function would be created and users could specify which arguments and return values should be Reflect. It could then be called via a new Function trait.

I think that could still work, but it will be a fair bit more involved, requiring some slightly more complex parsing. And it of course is a bit more work for the user, since they need to create the type via macro invocation.

It also makes registering these functions onto a type a bit more complicated (depending on how it's implemented).

For now, I think this is a fairly simple, yet powerful solution that provides the least amount of friction for users.


Showcase

Bevy now adds support for storing and calling functions dynamically using reflection!

// 1. Take a standard Rust function
fn add(left: i32, right: i32) -> i32 {
  left + right
}

// 2. Convert it into a type-erased `DynamicFunction` using the `IntoFunction` trait
let mut function: DynamicFunction = add.into_function();
// 3. Define your arguments from reflected values
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
// 4. Call the function with your arguments
let result: Return = function.call(args).unwrap();
// 5. Extract the return value
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);

Changelog

TL;DR

  • Added support for function reflection
  • Added a new Function Reflection example:
    //! This example demonstrates how functions can be called dynamically using reflection.
    //!
    //! Function reflection is useful for calling regular Rust functions in a dynamic context,
    //! where the types of arguments, return values, and even the function itself aren't known at compile time.
    //!
    //! This can be used for things like adding scripting support to your application,
    //! processing deserialized reflection data, or even just storing type-erased versions of your functions.
    use bevy::reflect::func::args::ArgInfo;
    use bevy::reflect::func::{
    ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo,
    };
    use bevy::reflect::Reflect;
    // Note that the `dbg!` invocations are used purely for demonstration purposes
    // and are not strictly necessary for the example to work.
    fn main() {
    // There are times when it may be helpful to store a function away for later.
    // In Rust, we can do this by storing either a function pointer or a function trait object.
    // For example, say we wanted to store the following function:
    fn add(left: i32, right: i32) -> i32 {
    left + right
    }
    // We could store it as either of the following:
    let fn_pointer: fn(i32, i32) -> i32 = add;
    let fn_trait_object: Box<dyn Fn(i32, i32) -> i32> = Box::new(add);
    // And we can call them like so:
    let result = fn_pointer(2, 2);
    assert_eq!(result, 4);
    let result = fn_trait_object(2, 2);
    assert_eq!(result, 4);
    // However, you'll notice that we have to know the types of the arguments and return value at compile time.
    // This means there's not really a way to store or call these functions dynamically at runtime.
    // Luckily, Bevy's reflection crate comes with a set of tools for doing just that!
    // We do this by first converting our function into the reflection-based `DynamicFunction` type
    // using the `IntoFunction` trait.
    let mut function: DynamicFunction = dbg!(add.into_function());
    // This time, you'll notice that `DynamicFunction` doesn't take any information about the function's arguments or return value.
    // This is because `DynamicFunction` checks the types of the arguments and return value at runtime.
    // Now we can generate a list of arguments:
    let args: ArgList = dbg!(ArgList::new().push_owned(2_i32).push_owned(2_i32));
    // And finally, we can call the function.
    // This returns a `Result` indicating whether the function was called successfully.
    // For now, we'll just unwrap it to get our `Return` value,
    // which is an enum containing the function's return value.
    let return_value: Return = dbg!(function.call(args).unwrap());
    // The `Return` value can be pattern matched or unwrapped to get the underlying reflection data.
    // For the sake of brevity, we'll just unwrap it here and downcast it to the expected type of `i32`.
    let value: Box<dyn Reflect> = return_value.unwrap_owned();
    assert_eq!(value.take::<i32>().unwrap(), 4);
    // The same can also be done for closures.
    let mut count = 0;
    let increment = |amount: i32| {
    count += amount;
    };
    let increment_function: DynamicFunction = dbg!(increment.into_function());
    let args = dbg!(ArgList::new().push_owned(5_i32));
    // `DynamicFunction`s containing closures that capture their environment like this one
    // may need to be dropped before those captured variables may be used again.
    // This can be done manually with `drop` or by using the `Function::call_once` method.
    dbg!(increment_function.call_once(args).unwrap());
    assert_eq!(count, 5);
    // As stated before, this works for many kinds of simple functions.
    // Functions with non-reflectable arguments or return values may not be able to be converted.
    // Generic functions are also not supported.
    // Additionally, the lifetime of the return value is tied to the lifetime of the first argument.
    // However, this means that many methods (i.e. functions with a `self` parameter) are also supported:
    #[derive(Reflect, Default)]
    struct Data {
    value: String,
    }
    impl Data {
    fn set_value(&mut self, value: String) {
    self.value = value;
    }
    // Note that only `&'static str` implements `Reflect`.
    // To get around this limitation we can use `&String` instead.
    fn get_value(&self) -> &String {
    &self.value
    }
    }
    let mut data = Data::default();
    let mut set_value = dbg!(Data::set_value.into_function());
    let args = dbg!(ArgList::new().push_mut(&mut data)).push_owned(String::from("Hello, world!"));
    dbg!(set_value.call(args).unwrap());
    assert_eq!(data.value, "Hello, world!");
    let mut get_value = dbg!(Data::get_value.into_function());
    let args = dbg!(ArgList::new().push_ref(&data));
    let return_value = dbg!(get_value.call(args).unwrap());
    let value: &dyn Reflect = return_value.unwrap_ref();
    assert_eq!(value.downcast_ref::<String>().unwrap(), "Hello, world!");
    // Lastly, for more complex use cases, you can always create a custom `DynamicFunction` manually.
    // This is useful for functions that can't be converted via the `IntoFunction` trait.
    // For example, this function doesn't implement `IntoFunction` due to the fact that
    // the lifetime of the return value is not tied to the lifetime of the first argument.
    fn get_or_insert(value: i32, container: &mut Option<i32>) -> &i32 {
    if container.is_none() {
    *container = Some(value);
    }
    container.as_ref().unwrap()
    }
    let mut get_or_insert_function = dbg!(DynamicFunction::new(
    |mut args, info| {
    let container_info = &info.args()[1];
    let value_info = &info.args()[0];
    // The `ArgList` contains the arguments in the order they were pushed.
    // Therefore, we need to pop them in reverse order.
    let container = args
    .pop()
    .unwrap()
    .take_mut::<Option<i32>>(container_info)
    .unwrap();
    let value = args.pop().unwrap().take_owned::<i32>(value_info).unwrap();
    Ok(Return::Ref(get_or_insert(value, container)))
    },
    FunctionInfo::new()
    // We can optionally provide a name for the function
    .with_name("get_or_insert")
    // Since our function takes arguments, we MUST provide that argument information.
    // The arguments should be provided in the order they are defined in the function.
    // This is used to validate any arguments given at runtime.
    .with_args(vec![
    ArgInfo::new::<i32>(0).with_name("value"),
    ArgInfo::new::<&mut Option<i32>>(1).with_name("container"),
    ])
    // We can optionally provide return information as well.
    .with_return_info(ReturnInfo::new::<&i32>()),
    ));
    let mut container: Option<i32> = None;
    let args = dbg!(ArgList::new().push_owned(5_i32).push_mut(&mut container));
    let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref();
    assert_eq!(value.downcast_ref::<i32>(), Some(&5));
    let args = dbg!(ArgList::new().push_owned(500_i32).push_mut(&mut container));
    let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref();
    assert_eq!(value.downcast_ref::<i32>(), Some(&5));
    }

Details

Added the following items:

  • ArgError enum
  • ArgId enum
  • ArgInfo struct
  • ArgList struct
  • Arg enum
  • DynamicFunction struct
  • FromArg trait (derived with derive(Reflect))
  • FunctionError enum
  • FunctionInfo struct
  • FunctionResult alias
  • GetOwnership trait (derived with derive(Reflect))
  • IntoFunction trait (with blanket implementation)
  • IntoReturn trait (derived with derive(Reflect))
  • Ownership enum
  • ReturnInfo struct
  • Return enum

@MrGVSV MrGVSV added C-Feature A new feature, making something new possible A-Reflection Runtime information about types labels Apr 30, 2024
@MrGVSV MrGVSV force-pushed the mrgvsv/reflect/reflect-functions branch 2 times, most recently from 888544b to 7af7456 Compare April 30, 2024 20:28
@alice-i-cecile alice-i-cecile added M-Needs-Release-Note Work that should be called out in the blog due to impact D-Complex Quite challenging from either a design or technical perspective. Ask for help! labels Apr 30, 2024
@MrGVSV
Copy link
Member Author

MrGVSV commented Apr 30, 2024

Hm, I'm debating on whether or not I should rename Function to Func. If we ever want to add a Function trait, then it might be good to do it now in order to avoid a rename. Or perhaps DynamicFunction would be a clearer name? 🤔

Copy link
Contributor

@cBournhonesque cBournhonesque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few comments but overall this looks good to me!

The example at the end is fantastic, and overall the new functionality is impressive

crates/bevy_reflect/bevy_reflect_derive/src/utility.rs Outdated Show resolved Hide resolved
}
}

impl #impl_generics #bevy_reflect::func::args::FromArg for &'static #type_path #ty_generics #where_reflect_clause {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be &'static because the output's 'from_arg could be any lifetime?
Could we have an impl for any lifetime longer than 'from_arg?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'from_arg will take the lifetime of the argument, so it could be any lifetime, including 'static. The &'static is just because we don't actually care about the lifetime of Self. I think you could even do impl<'a> FromArg for &'a Foo, but again we don't care about that lifetime, just the one for Item.

crates/bevy_reflect/src/func/args/info.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/func/args/list.rs Outdated Show resolved Hide resolved
///
/// [`Function`]: crate::func::Function
#[derive(Default, Debug)]
pub struct ArgList<'a>(Vec<Arg<'a>>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is why all the arguments need to have the same lifetime? Maybe it would be worth mentioning in a comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much. In one iteration I had originally used an enum in an effort to avoid Vec allocations:

enum ArgList<'a> {
  Arg0,
  Arg1(Arg<'a>),
  Arg2(Arg<'a>, Arg<'a>),
  Arg3(Arg<'a>, Arg<'a>, Arg<'a>),
  // ...
  Variadic(Vec<Arg<'a>>)
}

If we did that, we potentially could maintain some degree of lifetimes by just adding a bunch of lifetimes to ArgList:

enum ArgList<'a, 'b, 'c, 'default: 'a + 'b + 'c> {
  Arg0,
  Arg1(Arg<'a>),
  Arg2(Arg<'a>, Arg<'b>),
  Arg3(Arg<'a>, Arg<'b>, Arg<'c>),
  // ...
  Variadic(Vec<Arg<'default>>)
}

The output would still only be able to tie itself to the first argument's lifetime, but doing this would mean that we shouldn't need to shrink lifetimes down (most of the time).

So that might be something to explore.

Thoughts?

Copy link
Contributor

@cBournhonesque cBournhonesque May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the enum ArgList might work; and it wouldn't even require the Variadic variant no?
Every new arg added would update the ArgList from ArgN to ArgN+1.

Might be something to explore, but I think it's also ok to merge the functionality as is and explore this in a future PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the enum ArgList might work; and it wouldn't even require the Variadic variant no?

The Variadic would be needed so users could supply more arguments than we have variants for. While IntoFunction supports a limited number of arguments, users are still able to construct their Function manually, with as many arguments as they want.

Might be something to explore, but I think it's also ok to merge the functionality as is and explore this in a future PR

Yeah that might be a good idea. Any breakage would be minimal and really only apply to cases where the lifetime of ArgList is explicitly set by a user.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah definitely going to save this for a followup PR!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, please leave this to followup.

crates/bevy_reflect/src/func/function.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/func/into_function.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/func/mod.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/func/mod.rs Show resolved Hide resolved
@MrGVSV MrGVSV force-pushed the mrgvsv/reflect/reflect-functions branch from a33b961 to 5406c74 Compare May 5, 2024 18:18
@MrGVSV MrGVSV force-pushed the mrgvsv/reflect/reflect-functions branch from 1be1ad3 to 6a536e8 Compare May 22, 2024 22:27
@MrGVSV MrGVSV force-pushed the mrgvsv/reflect/reflect-functions branch from 6a536e8 to a70dd8a Compare May 22, 2024 22:53
@MrGVSV MrGVSV force-pushed the mrgvsv/reflect/reflect-functions branch from 3c0350f to b8ed0a8 Compare June 29, 2024 05:02
Closures no longer need to be `'static` as we now track the
lifetime of the wrapped function
This takes after similar concepts in bevy_ecs
@MrGVSV
Copy link
Member Author

MrGVSV commented Jun 29, 2024

Updated the PR description with the new DynamicFunction naming.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 29, 2024
@MrGVSV
Copy link
Member Author

MrGVSV commented Jun 29, 2024

Two updates:

  1. I realized that std::any::type_name could be used to automatically infer the function name, so I went ahead and made that the default
  2. DynamicFunction now implements IntoFunction, allowing it to be used interchangeably with an actual function pointer or closure wherever IntoFunction is expected

@MrGVSV
Copy link
Member Author

MrGVSV commented Jun 30, 2024

Talked about this on Discord with @NthTensor, but I think I may explore an IntoClosure/DynamicClosure split in a followup PR. This will make using a function registry a lot easier as we won't have to require a mutable reference in order to call purely-static functions.

@MrGVSV MrGVSV force-pushed the mrgvsv/reflect/reflect-functions branch from 9ab4702 to 91f249f Compare June 30, 2024 20:46
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jul 1, 2024
Merged via the queue into bevyengine:main with commit 276dd04 Jul 1, 2024
31 checks passed
@MrGVSV MrGVSV deleted the mrgvsv/reflect/reflect-functions branch July 1, 2024 16:22
zmbush pushed a commit to zmbush/bevy that referenced this pull request Jul 3, 2024
# Objective

We're able to reflect types sooooooo... why not functions?

The goal of this PR is to make functions callable within a dynamic
context, where type information is not readily available at compile
time.

For example, if we have a function:

```rust
fn add(left: i32, right: i32) -> i32 {
  left + right
}
```

And two `Reflect` values we've already validated are `i32` types:

```rust
let left: Box<dyn Reflect> = Box::new(2_i32);
let right: Box<dyn Reflect> = Box::new(2_i32);
```

We should be able to call `add` with these values:

```rust
// ?????
let result: Box<dyn Reflect> = add.call_dynamic(left, right);
```

And ideally this wouldn't just work for functions, but methods and
closures too!

Right now, users have two options:

1. Manually parse the reflected data and call the function themselves
2. Rely on registered type data to handle the conversions for them

For a small function like `add`, this isn't too bad. But what about for
more complex functions? What about for many functions?

At worst, this process is error-prone. At best, it's simply tedious.

And this is assuming we know the function at compile time. What if we
want to accept a function dynamically and call it with our own
arguments?

It would be much nicer if `bevy_reflect` could alleviate some of the
problems here.

## Solution

Added function reflection!

This adds a `DynamicFunction` type to wrap a function dynamically. This
can be called with an `ArgList`, which is a dynamic list of
`Reflect`-containing `Arg` arguments. It returns a `FunctionResult`
which indicates whether or not the function call succeeded, returning a
`Reflect`-containing `Return` type if it did succeed.

Many functions can be converted into this `DynamicFunction` type thanks
to the `IntoFunction` trait.

Taking our previous `add` example, this might look something like
(explicit types added for readability):

```rust
fn add(left: i32, right: i32) -> i32 {
  left + right
}

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

And it also works on closures:

```rust
let add = |left: i32, right: i32| left + right;

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

As well as methods:

```rust
#[derive(Reflect)]
struct Foo(i32);

impl Foo {
  fn add(&mut self, value: i32) {
    self.0 += value;
  }
}

let mut foo = Foo(2);

let mut function: DynamicFunction = Foo::add.into_function();
let args: ArgList = ArgList::new().push_mut(&mut foo).push_owned(2_i32);
function.call(args).unwrap();
assert_eq!(foo.0, 4);
```

### Limitations

While this does cover many functions, it is far from a perfect system
and has quite a few limitations. Here are a few of the limitations when
using `IntoFunction`:

1. The lifetime of the return value is only tied to the lifetime of the
first argument (useful for methods). This means you can't have a
function like `(a: i32, b: &i32) -> &i32` without creating the
`DynamicFunction` manually.
2. Only 15 arguments are currently supported. If the first argument is a
(mutable) reference, this number increases to 16.
3. Manual implementations of `Reflect` will need to implement the new
`FromArg`, `GetOwnership`, and `IntoReturn` traits in order to be used
as arguments/return types.

And some limitations of `DynamicFunction` itself:

1. All arguments share the same lifetime, or rather, they will shrink to
the shortest lifetime.
2. Closures that capture their environment may need to have their
`DynamicFunction` dropped before accessing those variables again (there
is a `DynamicFunction::call_once` to make this a bit easier)
3. All arguments and return types must implement `Reflect`. While not a
big surprise coming from `bevy_reflect`, this implementation could
actually still work by swapping `Reflect` out with `Any`. Of course,
that makes working with the arguments and return values a bit harder.
4. Generic functions are not supported (unless they have been manually
monomorphized)

And general, reflection gotchas:

1. `&str` does not implement `Reflect`. Rather, `&'static str`
implements `Reflect` (the same is true for `&Path` and similar types).
This means that `&'static str` is considered an "owned" value for the
sake of generating arguments. Additionally, arguments and return types
containing `&str` will assume it's `&'static str`, which is almost never
the desired behavior. In these cases, the only solution (I believe) is
to use `&String` instead.

### Followup Work

This PR is the first of two PRs I intend to work on. The second PR will
aim to integrate this new function reflection system into the existing
reflection traits and `TypeInfo`. The goal would be to register and call
a reflected type's methods dynamically.

I chose not to do that in this PR since the diff is already quite large.
I also want the discussion for both PRs to be focused on their own
implementation.

Another followup I'd like to do is investigate allowing common container
types as a return type, such as `Option<&[mut] T>` and `Result<&[mut] T,
E>`. This would allow even more functions to opt into this system. I
chose to not include it in this one, though, for the same reasoning as
previously mentioned.

### Alternatives

One alternative I had considered was adding a macro to convert any
function into a reflection-based counterpart. The idea would be that a
struct that wraps the function would be created and users could specify
which arguments and return values should be `Reflect`. It could then be
called via a new `Function` trait.

I think that could still work, but it will be a fair bit more involved,
requiring some slightly more complex parsing. And it of course is a bit
more work for the user, since they need to create the type via macro
invocation.

It also makes registering these functions onto a type a bit more
complicated (depending on how it's implemented).

For now, I think this is a fairly simple, yet powerful solution that
provides the least amount of friction for users.

---

## Showcase

Bevy now adds support for storing and calling functions dynamically
using reflection!

```rust
// 1. Take a standard Rust function
fn add(left: i32, right: i32) -> i32 {
  left + right
}

// 2. Convert it into a type-erased `DynamicFunction` using the `IntoFunction` trait
let mut function: DynamicFunction = add.into_function();
// 3. Define your arguments from reflected values
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
// 4. Call the function with your arguments
let result: Return = function.call(args).unwrap();
// 5. Extract the return value
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

## Changelog

#### TL;DR

- Added support for function reflection
- Added a new `Function Reflection` example:
https://github.com/bevyengine/bevy/blob/ba727898f2adff817838fc4cdb49871bbce37356/examples/reflection/function_reflection.rs#L1-L157

#### Details

Added the following items:

- `ArgError` enum
- `ArgId` enum
- `ArgInfo` struct
- `ArgList` struct
- `Arg` enum
- `DynamicFunction` struct
- `FromArg` trait (derived with `derive(Reflect)`)
- `FunctionError` enum
- `FunctionInfo` struct
- `FunctionResult` alias
- `GetOwnership` trait (derived with `derive(Reflect)`)
- `IntoFunction` trait (with blanket implementation)
- `IntoReturn` trait (derived with `derive(Reflect)`)
- `Ownership` enum
- `ReturnInfo` struct
- `Return` enum

---------

Co-authored-by: Periwink <charlesbour@gmail.com>
MrGVSV added a commit to MrGVSV/bevy that referenced this pull request Jul 5, 2024
# Objective

We're able to reflect types sooooooo... why not functions?

The goal of this PR is to make functions callable within a dynamic
context, where type information is not readily available at compile
time.

For example, if we have a function:

```rust
fn add(left: i32, right: i32) -> i32 {
  left + right
}
```

And two `Reflect` values we've already validated are `i32` types:

```rust
let left: Box<dyn Reflect> = Box::new(2_i32);
let right: Box<dyn Reflect> = Box::new(2_i32);
```

We should be able to call `add` with these values:

```rust
// ?????
let result: Box<dyn Reflect> = add.call_dynamic(left, right);
```

And ideally this wouldn't just work for functions, but methods and
closures too!

Right now, users have two options:

1. Manually parse the reflected data and call the function themselves
2. Rely on registered type data to handle the conversions for them

For a small function like `add`, this isn't too bad. But what about for
more complex functions? What about for many functions?

At worst, this process is error-prone. At best, it's simply tedious.

And this is assuming we know the function at compile time. What if we
want to accept a function dynamically and call it with our own
arguments?

It would be much nicer if `bevy_reflect` could alleviate some of the
problems here.

## Solution

Added function reflection!

This adds a `DynamicFunction` type to wrap a function dynamically. This
can be called with an `ArgList`, which is a dynamic list of
`Reflect`-containing `Arg` arguments. It returns a `FunctionResult`
which indicates whether or not the function call succeeded, returning a
`Reflect`-containing `Return` type if it did succeed.

Many functions can be converted into this `DynamicFunction` type thanks
to the `IntoFunction` trait.

Taking our previous `add` example, this might look something like
(explicit types added for readability):

```rust
fn add(left: i32, right: i32) -> i32 {
  left + right
}

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

And it also works on closures:

```rust
let add = |left: i32, right: i32| left + right;

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

As well as methods:

```rust
#[derive(Reflect)]
struct Foo(i32);

impl Foo {
  fn add(&mut self, value: i32) {
    self.0 += value;
  }
}

let mut foo = Foo(2);

let mut function: DynamicFunction = Foo::add.into_function();
let args: ArgList = ArgList::new().push_mut(&mut foo).push_owned(2_i32);
function.call(args).unwrap();
assert_eq!(foo.0, 4);
```

### Limitations

While this does cover many functions, it is far from a perfect system
and has quite a few limitations. Here are a few of the limitations when
using `IntoFunction`:

1. The lifetime of the return value is only tied to the lifetime of the
first argument (useful for methods). This means you can't have a
function like `(a: i32, b: &i32) -> &i32` without creating the
`DynamicFunction` manually.
2. Only 15 arguments are currently supported. If the first argument is a
(mutable) reference, this number increases to 16.
3. Manual implementations of `Reflect` will need to implement the new
`FromArg`, `GetOwnership`, and `IntoReturn` traits in order to be used
as arguments/return types.

And some limitations of `DynamicFunction` itself:

1. All arguments share the same lifetime, or rather, they will shrink to
the shortest lifetime.
2. Closures that capture their environment may need to have their
`DynamicFunction` dropped before accessing those variables again (there
is a `DynamicFunction::call_once` to make this a bit easier)
3. All arguments and return types must implement `Reflect`. While not a
big surprise coming from `bevy_reflect`, this implementation could
actually still work by swapping `Reflect` out with `Any`. Of course,
that makes working with the arguments and return values a bit harder.
4. Generic functions are not supported (unless they have been manually
monomorphized)

And general, reflection gotchas:

1. `&str` does not implement `Reflect`. Rather, `&'static str`
implements `Reflect` (the same is true for `&Path` and similar types).
This means that `&'static str` is considered an "owned" value for the
sake of generating arguments. Additionally, arguments and return types
containing `&str` will assume it's `&'static str`, which is almost never
the desired behavior. In these cases, the only solution (I believe) is
to use `&String` instead.

### Followup Work

This PR is the first of two PRs I intend to work on. The second PR will
aim to integrate this new function reflection system into the existing
reflection traits and `TypeInfo`. The goal would be to register and call
a reflected type's methods dynamically.

I chose not to do that in this PR since the diff is already quite large.
I also want the discussion for both PRs to be focused on their own
implementation.

Another followup I'd like to do is investigate allowing common container
types as a return type, such as `Option<&[mut] T>` and `Result<&[mut] T,
E>`. This would allow even more functions to opt into this system. I
chose to not include it in this one, though, for the same reasoning as
previously mentioned.

### Alternatives

One alternative I had considered was adding a macro to convert any
function into a reflection-based counterpart. The idea would be that a
struct that wraps the function would be created and users could specify
which arguments and return values should be `Reflect`. It could then be
called via a new `Function` trait.

I think that could still work, but it will be a fair bit more involved,
requiring some slightly more complex parsing. And it of course is a bit
more work for the user, since they need to create the type via macro
invocation.

It also makes registering these functions onto a type a bit more
complicated (depending on how it's implemented).

For now, I think this is a fairly simple, yet powerful solution that
provides the least amount of friction for users.

---

## Showcase

Bevy now adds support for storing and calling functions dynamically
using reflection!

```rust
// 1. Take a standard Rust function
fn add(left: i32, right: i32) -> i32 {
  left + right
}

// 2. Convert it into a type-erased `DynamicFunction` using the `IntoFunction` trait
let mut function: DynamicFunction = add.into_function();
// 3. Define your arguments from reflected values
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
// 4. Call the function with your arguments
let result: Return = function.call(args).unwrap();
// 5. Extract the return value
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

## Changelog

#### TL;DR

- Added support for function reflection
- Added a new `Function Reflection` example:
https://github.com/bevyengine/bevy/blob/ba727898f2adff817838fc4cdb49871bbce37356/examples/reflection/function_reflection.rs#L1-L157

#### Details

Added the following items:

- `ArgError` enum
- `ArgId` enum
- `ArgInfo` struct
- `ArgList` struct
- `Arg` enum
- `DynamicFunction` struct
- `FromArg` trait (derived with `derive(Reflect)`)
- `FunctionError` enum
- `FunctionInfo` struct
- `FunctionResult` alias
- `GetOwnership` trait (derived with `derive(Reflect)`)
- `IntoFunction` trait (with blanket implementation)
- `IntoReturn` trait (derived with `derive(Reflect)`)
- `Ownership` enum
- `ReturnInfo` struct
- `Return` enum

---------

Co-authored-by: Periwink <charlesbour@gmail.com>
github-merge-queue bot pushed a commit that referenced this pull request Jul 5, 2024
# Objective

Looks like I accidentally disabled the reflection compile fail tests in
#13152. These should be re-enabled.

## Solution

Re-enable reflection compile fail tests.

## Testing

CI should pass. You can also test locally by navigating to
`crates/bevy_reflect/compile_fail/` and running:

```
cargo test --target-dir ../../../target
```
SkiFire13 added a commit to SkiFire13/bevy that referenced this pull request Jul 11, 2024
github-merge-queue bot pushed a commit that referenced this pull request Jul 16, 2024
# Objective

As mentioned in
[this](#13152 (comment))
comment, creating a function registry (see #14098) is a bit difficult
due to the requirements of `DynamicFunction`. Internally, a
`DynamicFunction` contains a `Box<dyn FnMut>` (the function that reifies
reflected arguments and calls the actual function), which requires `&mut
self` in order to be called.

This means that users would require a mutable reference to the function
registry for it to be useful— which isn't great. And they can't clone
the `DynamicFunction` either because cloning an `FnMut` isn't really
feasible (wrapping it in an `Arc` would allow it to be cloned but we
wouldn't be able to call the clone since we need a mutable reference to
the `FnMut`, which we can't get with multiple `Arc`s still alive,
requiring us to also slap in a `Mutex`, which adds additional overhead).

And we don't want to just replace the `dyn FnMut` with `dyn Fn` as that
would prevent reflecting closures that mutate their environment.

Instead, we need to introduce a new type to split the requirements of
`DynamicFunction`.

## Solution

Introduce new types for representing closures.

Specifically, this PR introduces `DynamicClosure` and
`DynamicClosureMut`. Similar to how `IntoFunction` exists for
`DynamicFunction`, two new traits were introduced: `IntoClosure` and
`IntoClosureMut`.

Now `DynamicFunction` stores a `dyn Fn` with a `'static` lifetime.
`DynamicClosure` also uses a `dyn Fn` but has a lifetime, `'env`, tied
to its environment. `DynamicClosureMut` is most like the old
`DynamicFunction`, keeping the `dyn FnMut` and also typing its lifetime,
`'env`, to the environment

Here are some comparison tables:

|   | `DynamicFunction` | `DynamicClosure` | `DynamicClosureMut` |
| - | ----------------- | ---------------- | ------------------- |
| Callable with `&self` | ✅ | ✅ | ❌ |
| Callable with `&mut self` | ✅ | ✅ | ✅ |
| Allows for non-`'static` lifetimes | ❌ | ✅ | ✅ |

|   | `IntoFunction` | `IntoClosure` | `IntoClosureMut` |
| - | -------------- | ------------- | ---------------- |
| Convert `fn` functions | ✅ | ✅ | ✅ |
| Convert `fn` methods | ✅ | ✅ | ✅ |
| Convert anonymous functions | ✅ | ✅ | ✅ |
| Convert closures that capture immutable references | ❌ | ✅ | ✅ |
| Convert closures that capture mutable references | ❌ | ❌ | ✅ |
| Convert closures that capture owned values | ❌[^1] | ✅ | ✅ |

[^1]: Due to limitations in Rust, `IntoFunction` can't be implemented
for just functions (unless we forced users to manually coerce them to
function pointers first). So closures that meet the trait requirements
_can technically_ be converted into a `DynamicFunction` as well. To both
future-proof and reduce confusion, though, we'll just pretend like this
isn't a thing.

```rust
let mut list: Vec<i32> = vec![1, 2, 3];

// `replace` is a closure that captures a mutable reference to `list`
let mut replace = |index: usize, value: i32| -> i32 {
  let old_value = list[index];
  list[index] = value;
  old_value
};

// Convert the closure into a dynamic closure using `IntoClosureMut::into_closure_mut`
let mut func: DynamicClosureMut = replace.into_closure_mut();

// Dynamically call the closure:
let args = ArgList::default().push_owned(1_usize).push_owned(-2_i32);
let value = func.call_once(args).unwrap().unwrap_owned();

// Check the result:
assert_eq!(value.take::<i32>().unwrap(), 2);
assert_eq!(list, vec![1, -2, 3]);
```

### `ReflectFn`/`ReflectFnMut`

To make extending the function reflection system easier (the blanket
impls for `IntoFunction`, `IntoClosure`, and `IntoClosureMut` are all
incredibly short), this PR generalizes callables with two new traits:
`ReflectFn` and `ReflectFnMut`.

These traits mimic `Fn` and `FnMut` but allow for being called via
reflection. In fact, their blanket implementations are identical save
for `ReflectFn` being implemented over `Fn` types and `ReflectFnMut`
being implemented over `FnMut` types.

And just as `Fn` is a subtrait of `FnMut`, `ReflectFn` is a subtrait of
`ReflectFnMut`. So anywhere that expects a `ReflectFnMut` can also be
given a `ReflectFn`.

To reiterate, these traits aren't 100% necessary. They were added in
purely for extensibility. If we decide to split things up differently or
add new traits/types in the future, then those changes should be much
simpler to implement.

### `TypedFunction`

Because of the split into `ReflectFn` and `ReflectFnMut`, we needed a
new way to access the function type information. This PR moves that
concept over into `TypedFunction`.

Much like `Typed`, this provides a way to access a function's
`FunctionInfo`.

By splitting this trait out, it helps to ensure the other traits are
focused on a single responsibility.

### Internal Macros

The original function PR (#13152) implemented `IntoFunction` using a
macro which was passed into an `all_tuples!` macro invocation. Because
we needed the same functionality for these new traits, this PR has
copy+pasted that code for `ReflectFn`, `ReflectFnMut`, and
`TypedFunction`— albeit with some differences between them.

Originally, I was going to try and macro-ify the impls and where clauses
such that we wouldn't have to straight up duplicate a lot of this logic.
However, aside from being more complex in general, autocomplete just
does not play nice with such heavily nested macros (tried in both
RustRover and VSCode). And both of those problems told me that it just
wasn't worth it: we need to ensure the crate is easily maintainable,
even at the cost of duplicating code.

So instead, I made sure to simplify the macro code by removing all
fully-qualified syntax and cutting the where clauses down to the bare
essentials, which helps to clean up a lot of the visual noise. I also
tried my best to document the macro logic in certain areas (I may even
add a bit more) to help with maintainability for future devs.

### Documentation

Documentation for this module was a bit difficult for me. So many of
these traits and types are very interconnected. And each trait/type has
subtle differences that make documenting it in a single place, like at
the module level, difficult to do cleanly. Describing the valid
signatures is also challenging to do well.

Hopefully what I have here is okay. I think I did an okay job, but let
me know if there any thoughts on ways to improve it. We can also move
such a task to a followup PR for more focused discussion.

## Testing

You can test locally by running:

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

---

## Changelog

- Added `DynamicClosure` struct
- Added `DynamicClosureMut` struct
- Added `IntoClosure` trait
- Added `IntoClosureMut` trait
- Added `ReflectFn` trait
- Added `ReflectFnMut` trait
- Added `TypedFunction` trait
- `IntoFunction` now only works for standard Rust functions
- `IntoFunction` no longer takes a lifetime parameter
- `DynamicFunction::call` now only requires `&self`
- Removed `DynamicFunction::call_once`
- Changed the `IntoReturn::into_return` signature to include a where
clause

## Internal Migration Guide

> [!important]
> Function reflection was introduced as part of the 0.15 dev cycle. This
migration guide was written for developers relying on `main` during this
cycle, and is not a breaking change coming from 0.14.

### `IntoClosure`

`IntoFunction` now only works for standard Rust functions. Calling
`IntoFunction::into_function` on a closure that captures references to
its environment (either mutable or immutable), will no longer compile.

Instead, you will need to use either `IntoClosure::into_closure` to
create a `DynamicClosure` or `IntoClosureMut::into_closure_mut` to
create a `DynamicClosureMut`, depending on your needs:

```rust
let punct = String::from("!");
let print = |value: String| {
    println!("{value}{punct}");
};

// BEFORE
let func: DynamicFunction = print.into_function();

// AFTER
let func: DynamicClosure = print.into_closure();
```

### `IntoFunction` lifetime

Additionally, `IntoFunction` no longer takes a lifetime parameter as it
always expects a `'static` lifetime. Usages will need to remove any
lifetime parameters:

```rust
// BEFORE
fn execute<'env, F: IntoFunction<'env, Marker>, Marker>(f: F) {/* ... */}

// AFTER
fn execute<F: IntoFunction<Marker>, Marker>(f: F) {/* ... */}
```

### `IntoReturn`

`IntoReturn::into_return` now has a where clause. Any manual
implementors will need to add this where clause to their implementation.
github-merge-queue bot pushed a commit that referenced this pull request Aug 6, 2024
# Objective

#13152 added support for reflecting functions. Now, we need a way to
register those functions such that they may be accessed anywhere within
the ECS.

## Solution

Added a `FunctionRegistry` type similar to `TypeRegistry`.

This allows a function to be registered and retrieved by name.

```rust
fn foo() -> i32 {
    123
}

let mut registry = FunctionRegistry::default();
registry.register("my_function", foo);

let function = registry.get_mut("my_function").unwrap();
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
assert_eq!(value.downcast_ref::<i32>(), Some(&123));
```

Additionally, I added an `AppFunctionRegistry` resource which wraps a
`FunctionRegistryArc`. Functions can be registered into this resource
using `App::register_function` or by getting a mutable reference to the
resource itself.

### Limitations

#### `Send + Sync`

In order to get this registry to work across threads, it needs to be
`Send + Sync`. This means that `DynamicFunction` needs to be `Send +
Sync`, which means that its internal function also needs to be `Send +
Sync`.

In most cases, this won't be an issue because standard Rust functions
(the type most likely to be registered) are always `Send + Sync`.
Additionally, closures tend to be `Send + Sync` as well, granted they
don't capture any `!Send` or `!Sync` variables.

This PR adds this `Send + Sync` requirement, but as mentioned above, it
hopefully shouldn't be too big of an issue.

#### Closures

Unfortunately, closures can't be registered yet. This will likely be
explored and added in a followup PR.

### Future Work

Besides addressing the limitations listed above, another thing we could
look into is improving the lookup of registered functions. One aspect is
in the performance of hashing strings. The other is in the developer
experience of having to call `std::any::type_name_of_val` to get the
name of their function (assuming they didn't give it a custom name).

## Testing

You can run the tests locally with:

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

---

## Changelog

- Added `FunctionRegistry`
- Added `AppFunctionRegistry` (a `Resource` available from `bevy_ecs`)
- Added `FunctionRegistryArc`
- Added `FunctionRegistrationError`
- Added `reflect_functions` feature to `bevy_ecs` and `bevy_app`
- `FunctionInfo` is no longer `Default`
- `DynamicFunction` now requires its wrapped function be `Send + Sync`

## Internal Migration Guide

> [!important]
> Function reflection was introduced as part of the 0.15 dev cycle. This
migration guide was written for developers relying on `main` during this
cycle, and is not a breaking change coming from 0.14.

`DynamicFunction` (both those created manually and those created with
`IntoFunction`), now require `Send + Sync`. All standard Rust functions
should meet that requirement. Closures, on the other hand, may not if
they capture any `!Send` or `!Sync` variables from its environment.
github-merge-queue bot pushed a commit that referenced this pull request Sep 22, 2024
# Objective

While #13152 added function reflection, it didn't really make functions
reflectable. Instead, it made it so that they can be called with
reflected arguments and return reflected data. But functions themselves
cannot be reflected.

In other words, we can't go from `DynamicFunction` to `dyn
PartialReflect`.

## Solution

Allow `DynamicFunction` to actually be reflected.

This PR adds the `Function` reflection subtrait (and corresponding
`ReflectRef`, `ReflectKind`, etc.). With this new trait, we're able to
implement `PartialReflect` on `DynamicFunction`.

### Implementors

`Function` is currently only implemented for `DynamicFunction<'static>`.
This is because we can't implement it generically over all
functions—even those that implement `IntoFunction`.

What about `DynamicFunctionMut`? Well, this PR does **not** implement
`Function` for `DynamicFunctionMut`.

The reasons for this are a little complicated, but it boils down to
mutability. `DynamicFunctionMut` requires `&mut self` to be invoked
since it wraps a `FnMut`. However, we can't really model this well with
`Function`. And if we make `DynamicFunctionMut` wrap its internal
`FnMut` in a `Mutex` to allow for `&self` invocations, then we run into
either concurrency issues or recursion issues (or, in the worst case,
both).

So for the time-being, we won't implement `Function` for
`DynamicFunctionMut`. It will be better to evaluate it on its own. And
we may even consider the possibility of removing it altogether if it
adds too much complexity to the crate.

### Dynamic vs Concrete

One of the issues with `DynamicFunction` is the fact that it's both a
dynamic representation (like `DynamicStruct` or `DynamicList`) and the
only way to represent a function.

Because of this, it's in a weird middle ground where we can't easily
implement full-on `Reflect`. That would require `Typed`, but what static
`TypeInfo` could it provide? Just that it's a `DynamicFunction`? None of
the other dynamic types implement `Typed`.

However, by not implementing `Reflect`, we lose the ability to downcast
back to our `DynamicStruct`. Our only option is to call
`Function::clone_dynamic`, which clones the data rather than by simply
downcasting. This works in favor of the `PartialReflect::try_apply`
implementation since it would have to clone anyways, but is definitely
not ideal. This is also the reason I had to add `Debug` as a supertrait
on `Function`.

For now, this PR chooses not to implement `Reflect` for
`DynamicFunction`. We may want to explore this in a followup PR (or even
this one if people feel strongly that it's strictly required).

The same is true for `FromReflect`. We may decide to add an
implementation there as well, but it's likely out-of-scope of this PR.

## Testing

You can test locally by running:

```
cargo test --package bevy_reflect --all-features
```

---

## Showcase

You can now pass around a `DynamicFunction` as a `dyn PartialReflect`!
This also means you can use it as a field on a reflected type without
having to ignore it (though you do need to opt out of `FromReflect`).

```rust
#[derive(Reflect)]
#[reflect(from_reflect = false)]
struct ClickEvent {
    callback: DynamicFunction<'static>,
}

let event: Box<dyn Struct> = Box::new(ClickEvent {
    callback: (|| println!("Clicked!")).into_function(),
});

// We can access our `DynamicFunction` as a `dyn PartialReflect`
let callback: &dyn PartialReflect = event.field("callback").unwrap();

// And access function-related methods via the new `Function` trait
let ReflectRef::Function(callback) = callback.reflect_ref() else {
    unreachable!()
};

// Including calling the function
callback.reflect_call(ArgList::new()).unwrap(); // Prints: Clicked!
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Reflection Runtime information about types C-Feature A new feature, making something new possible D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

3 participants