-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
bevy_reflect: Anonymous function parsing #14641
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: Anonymous function parsing #14641
Conversation
560f4b7 to
46f8847
Compare
alice-i-cecile
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with the reasoning in the PR description: if this breaks in the future, it's not vital.
Code quality and docs are impeccable: no notes.
|
Seems like a good compromise in my opinion. Worth mentioning that we also aren't really dependent on type names as unique identifiers, in light of all the business we're doing to accommodate closures and anonymous functions here. |
|
@MrGVSV once CI is green I'll merge this in :) |
`register` no longer takes a name, `register_dynamic` has been removed, and `register_with_name` has been added. Similar changes to the overwriting methods
46f8847 to
446622d
Compare
…names (#14666) # Objective As pointed out by @SkiFire13 on [Discord](https://discord.com/channels/691052431525675048/1002362493634629796/1270624366119485441), I was incorrect in #14641 regarding the type name of anonymous functions. I had stated that they will return something like `fn(i32, i32) -> i32`, but this is wrong. They actually behave like closures (despite not technically being closures) and return something more like `foo::bar::{{closure}}`. This isn't a major issue because the reasoning behind #14641 still stands. However, the internal documentation should probably be updated so future contributors don't believe the lies I left behind. ## Solution Updated the internal documentation for `create_info` to reflect the actual type name of an anonymous function. In that same module, I also added a test for function pointers and updated all tests to include sanity checks for the `std::any::type_name` of each category of callable. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ```
Objective
Note
There are some inaccuracies in this PR and its description regarding the type name of anonymous functions. However, the goal and effectiveness of the PR remains. Please see #14666 for details.
TL;DR
#14098 added the
FunctionRegistrybut had some last minute complications due to anonymous functions. It ended up going with a "required name" approach to ensure anonymous functions would always have a name.However, this approach isn't ideal for named functions since, by definition, they will always have a name.
Therefore, this PR aims to modify function reflection such that we can make function registration easier for named functions, while still allowing anonymous functions to be registered as well.
Context
Function registration (#14098) ran into a little problem: anonymous functions.
Anonymous functions, including function pointers, have very non-unique type names. For example, the anonymous function
|a: i32, b: i32| a + bhas the type name offn(i32, i32) -> i32. This obviously means we'd conflict with another function like|a: i32, b: i32| a - b.The solution that #14098 landed on was to always require a name during function registration.
The downside with this is that named functions (e.g.
fn add(a: i32, b: i32) -> i32 { a + b }) had to redundantly provide a name. Additionally, manually constructedDynamicFunctions also ran into this ergonomics issue.I don't entirely know how the function registry will be used, but I have a strong suspicion that most of its registrations will either be named functions or manually constructed
DynamicFunctions, with anonymous functions only being used here and there for quick prototyping or adding small functionality.Why then should the API prioritize the anonymous function use case by always requiring a name during registration?
Telling Functions Apart
Rust doesn't provide a lot of out-of-the-box tools for reflecting functions. One of the biggest hurdles in attempting to solve the problem outlined above would be to somehow tell the different kinds of functions apart.
Let's briefly recap on the categories of functions in Rust:
fn add(a: i32, b: i32) -> i32 { a + b }|a: i32| a + captured_variable|a: i32, b: i32| a + bfn(i32, i32) -> i32My first thought was to try and differentiate these categories based on their size. However, we can see that this doesn't quite work:
size_ofNot only does this not tell anonymous functions from named ones, but it struggles with pretty much all of them.
My second then was to differentiate based on type name:
type_namefoo::bar::bazfoo::bar::{{closure}}fn() -> Stringfn() -> StringThis is much better. While it can't distinguish between function pointers and anonymous functions, this doesn't matter too much since we only care about whether we can name the function.
So why didn't we implement this in #14098?
Relying on
type_nameWhile this solution was known about while working on #14098, it was left out from that PR due to it being potentially controversial.
The docs for
std::any::type_namestate:So that's it then? We can't use
type_name?Well, this statement isn't so much a rule as it is a guideline. And Bevy is no stranger to bending the rules to make things work or to improve ergonomics. Remember that before
TypePath, Bevy's scene system was entirely dependent ontype_name. Not to mention thattype_nameis being used as a key into both theTypeRegistryand theFunctionRegistry.Bevy's practices aside, can we reliably use
type_namefor this?My answer would be "yes".
Anonymous functions are anonymous. They have no name. There's nothing Rust could do to give them a name apart from generating a random string of characters. But remember that this is a diagnostic tool, it doesn't make sense to obfuscate the type by randomizing the output. So changing it to be anything other than what it is now is very unlikely.
The only changes that I could potentially see happening are:
{{closure}}with the name of their variableI don't think the first is likely to happen, but if it does then it actually works out in our favor: closures are now named!
The second point is probably the likeliest. However, adding lifetimes doesn't mean we can't still rely on
type_nameto determine whether or not a function is named. So we should be okay in this case as well.Solution
Parse the
type_nameof the function in theTypedFunctionimpl to determine if the function is named or anonymous.This once again makes
FunctionInfo::nameoptional. For manual constructions ofDynamicFunction,FunctionInfo::namedor ``FunctionInfo::anonymous` can be used.The
FunctionRegistryAPI has also been reworked to account for this change.FunctionRegistry::registerno longer takes a name and instead takes it from the supplied function, returning aFunctionRegistrationError::MissingNameerror if the name isNone. This also doubles as a replacement for the oldFunctionRegistry::register_dynamicmethod, which has been removed.To handle anonymous functions, a
FunctionRegistry::register_with_namemethod has been added. This works in the same wayFunctionRegistry::registerused to work before this PR.The overwriting methods have been updated in a similar manner, with modifications to
FunctionRegistry::overwrite_registration, the removal ofFunctionRegistry::overwrite_registration_dynamic, and the addition ofFunctionRegistry::overwrite_registration_with_name.This PR also updates the methods on
Appin a similar way:App::register_functionno longer requires a name argument andApp::register_function_with_namehas been added to handle anonymous functions (and eventually closures).Testing
You can run the tests locally by running:
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
mainduring this cycle, and is not a breaking change coming from 0.14.Note
This list is not exhaustive. It only contains some of the most important changes.
FunctionRegistry::registerno longer requires a name string for named functions. Anonymous functions, however, need to be registered usingFunctionRegistry::register_with_name.FunctionInfo::nameis now optional. Anonymous functions and closures will now have their name set toNoneby default. Additionally,FunctionInfo::newhas been renamed toFunctionInfo::named.