Crate bevy_reflect
source ·Expand description
Reflection in Rust.
Reflection is a powerful tool provided within many programming languages that allows for meta-programming: using information about the program to affect the program. In other words, reflection allows us to inspect the program itself, its syntax, and its type information at runtime.
This crate adds this missing reflection functionality to Rust. Though it was made with the Bevy game engine in mind, it’s a general-purpose solution that can be used in any Rust project.
At a very high level, this crate allows you to:
- Dynamically interact with Rust values
- Access type metadata at runtime
- Serialize and deserialize (i.e. save and load) data
It’s important to note that because of missing features in Rust, there are some limitations with this crate.
The Reflect
Trait
At the core of bevy_reflect
is the Reflect
trait.
One of its primary purposes is to allow all implementors to be passed around
as a dyn Reflect
trait object.
This allows any such type to be operated upon completely dynamically (at a small runtime cost).
Implementing the trait is easily done using the provided derive macro:
#[derive(Reflect)]
struct MyStruct {
foo: i32
}
This will automatically generate the implementation of Reflect
for any struct or enum.
It will also generate other very important trait implementations used for reflection:
GetTypeRegistration
Typed
Struct
,TupleStruct
, orEnum
depending on the type
Requirements
We can implement Reflect
on any type that satisfies both of the following conditions:
- The type implements
Any
. This is true if and only if the type itself has a'static
lifetime. - All fields and sub-elements themselves implement
Reflect
(see the derive macro documentation for details on how to ignore certain fields when deriving).
Additionally, using the derive macro on enums requires a third condition to be met:
- All fields and sub-elements must implement
FromReflect
— another important reflection trait discussed in a later section.
The Reflect
Subtraits
Since Reflect
is meant to cover any and every type, this crate also comes with a few
more traits to accompany Reflect
and provide more specific interactions.
We refer to these traits as the reflection subtraits since they all have Reflect
as a supertrait.
The current list of reflection subtraits include:
As mentioned previously, the last three are automatically implemented by the derive macro.
Each of these traits come with their own methods specific to their respective category.
For example, we can access our struct’s fields by name using the Struct::field
method.
let my_struct: Box<dyn Struct> = Box::new(MyStruct {
foo: 123
});
let foo: &dyn Reflect = my_struct.field("foo").unwrap();
assert_eq!(Some(&123), foo.downcast_ref::<i32>());
Since most data is passed around as dyn Reflect
,
the Reflect
trait has methods for going to and from these subtraits.
Reflect::reflect_kind
, Reflect::reflect_ref
, Reflect::reflect_mut
, and Reflect::reflect_owned
all return
an enum that respectively contains zero-sized, immutable, mutable, and owned access to the type as a subtrait object.
For example, we can get out a dyn Tuple
from our reflected tuple type using one of these methods.
let my_tuple: Box<dyn Reflect> = Box::new((1, 2, 3));
let ReflectRef::Tuple(my_tuple) = my_tuple.reflect_ref() else { unreachable!() };
assert_eq!(3, my_tuple.field_len());
And to go back to a general-purpose dyn Reflect
,
we can just use the matching Reflect::as_reflect
, Reflect::as_reflect_mut
,
or Reflect::into_reflect
methods.
Value Types
Types that do not fall under one of the above subtraits,
such as for primitives (e.g. bool
, usize
, etc.)
and simple types (e.g. String
, Duration
),
are referred to as value types
since methods like Reflect::reflect_ref
return a ReflectRef::Value
variant.
While most other types contain their own dyn Reflect
fields and data,
these types generally cannot be broken down any further.
Dynamic Types
Each subtrait comes with a corresponding dynamic type.
The available dynamic types are:
These dynamic types may contain any arbitrary reflected data.
let mut data = DynamicStruct::default();
data.insert("foo", 123_i32);
assert_eq!(Some(&123), data.field("foo").unwrap().downcast_ref::<i32>())
They are most commonly used as “proxies” for other types,
where they contain the same data as— and therefore, represent— a concrete type.
The Reflect::clone_value
method will return a dynamic type for all non-value types,
allowing all types to essentially be “cloned”.
And since dynamic types themselves implement Reflect
,
we may pass them around just like any other reflected type.
let original: Box<dyn Reflect> = Box::new(MyStruct {
foo: 123
});
// `cloned` will be a `DynamicStruct` representing a `MyStruct`
let cloned: Box<dyn Reflect> = original.clone_value();
assert!(cloned.represents::<MyStruct>());
assert!(cloned.is::<DynamicStruct>());
Patching
These dynamic types come in handy when needing to apply multiple changes to another type.
This is known as “patching” and is done using the Reflect::apply
method.
let mut value = Some(123_i32);
let patch = DynamicEnum::new("None", ());
value.apply(&patch);
assert_eq!(None, value);
FromReflect
It’s important to remember that dynamic types are not the concrete type they may be representing. A common mistake is to treat them like such when trying to cast back to the original type or when trying to make use of a reflected trait which expects the actual type.
let original: Box<dyn Reflect> = Box::new(MyStruct {
foo: 123
});
let cloned: Box<dyn Reflect> = original.clone_value();
let value = cloned.take::<MyStruct>().unwrap(); // PANIC!
To resolve this issue, we’ll need to convert the dynamic type to the concrete one.
This is where FromReflect
comes in.
FromReflect
is a trait that allows an instance of a type to be generated from a
dynamic representation— even partial ones.
And since the FromReflect::from_reflect
method takes the data by reference,
this can be used to effectively clone data (to an extent).
It is automatically implemented when deriving Reflect
on a type unless opted out of
using #[reflect(from_reflect = false)]
on the item.
#[derive(Reflect)]
struct MyStruct {
foo: i32
}
let original: Box<dyn Reflect> = Box::new(MyStruct {
foo: 123
});
let cloned: Box<dyn Reflect> = original.clone_value();
let value = <MyStruct as FromReflect>::from_reflect(&*cloned).unwrap(); // OK!
When deriving, all active fields and sub-elements must also implement FromReflect
.
Fields can be given default values for when a field is missing in the passed value or even ignored.
Ignored fields must either implement Default
or have a default function specified
using #[reflect(default = "path::to::function")]
.
See the derive macro documentation for details.
All primitives and simple types implement FromReflect
by relying on their Default
implementation.
Path navigation
The GetPath
trait allows accessing arbitrary nested fields of a Reflect
type.
Using GetPath
, it is possible to use a path string to access a specific field
of a reflected type.
#[derive(Reflect)]
struct MyStruct {
value: Vec<Option<u32>>
}
let my_struct = MyStruct {
value: vec![None, None, Some(123)],
};
assert_eq!(
my_struct.path::<u32>(".value[2].0").unwrap(),
&123,
);
Type Registration
This crate also comes with a TypeRegistry
that can be used to store and retrieve additional type metadata at runtime,
such as helper types and trait implementations.
The derive macro for Reflect
also generates an implementation of the GetTypeRegistration
trait,
which is used by the registry to generate a TypeRegistration
struct for that type.
We can then register additional type data we want associated with that type.
For example, we can register ReflectDefault
on our type so that its Default
implementation
may be used dynamically.
#[derive(Reflect, Default)]
struct MyStruct {
foo: i32
}
let mut registry = TypeRegistry::empty();
registry.register::<MyStruct>();
registry.register_type_data::<MyStruct, ReflectDefault>();
let registration = registry.get(std::any::TypeId::of::<MyStruct>()).unwrap();
let reflect_default = registration.data::<ReflectDefault>().unwrap();
let new_value: Box<dyn Reflect> = reflect_default.default();
assert!(new_value.is::<MyStruct>());
Because this operation is so common, the derive macro actually has a shorthand for it.
By using the #[reflect(Trait)]
attribute, the derive macro will automatically register a matching,
in-scope ReflectTrait
type within the GetTypeRegistration
implementation.
use bevy_reflect::prelude::{Reflect, ReflectDefault};
#[derive(Reflect, Default)]
#[reflect(Default)]
struct MyStruct {
foo: i32
}
Reflecting Traits
Type data doesn’t have to be tied to a trait, but it’s often extremely useful to create trait type data.
These allow traits to be used directly on a dyn Reflect
while utilizing the underlying type’s implementation.
For any object-safe trait, we can easily generate a corresponding ReflectTrait
type for our trait
using the #[reflect_trait]
macro.
#[reflect_trait] // Generates a `ReflectMyTrait` type
pub trait MyTrait {}
impl<T: Reflect> MyTrait for T {}
let mut registry = TypeRegistry::new();
registry.register_type_data::<i32, ReflectMyTrait>();
The generated type data can be used to convert a valid dyn Reflect
into a dyn MyTrait
.
See the trait reflection example
for more information and usage details.
Serialization
By using reflection, we are also able to get serialization capabilities for free.
In fact, using bevy_reflect
can result in faster compile times and reduced code generation over
directly deriving the serde
traits.
The way it works is by moving the serialization logic into common serializers and deserializers:
All of these structs require a reference to the registry so that type information can be retrieved,
as well as registered type data, such as ReflectSerialize
and ReflectDeserialize
.
The general entry point are the “untyped” versions of these structs. These will automatically extract the type information and pass them into their respective “typed” version.
The output of the ReflectSerializer
will be a map, where the key is the type path
and the value is the serialized data.
The TypedReflectSerializer
will simply output the serialized data.
The UntypedReflectDeserializer
can be used to deserialize this map and return a Box<dyn Reflect>
,
where the underlying type will be a dynamic type representing some concrete type (except for value types).
Again, it’s important to remember that dynamic types may need to be converted to their concrete counterparts
in order to be used in certain cases.
This can be achieved using FromReflect
.
#[derive(Reflect, PartialEq, Debug)]
struct MyStruct {
foo: i32
}
let original_value = MyStruct {
foo: 123
};
// Register
let mut registry = TypeRegistry::new();
registry.register::<MyStruct>();
// Serialize
let reflect_serializer = ReflectSerializer::new(&original_value, ®istry);
let serialized_value: String = ron::to_string(&reflect_serializer).unwrap();
// Deserialize
let reflect_deserializer = UntypedReflectDeserializer::new(®istry);
let deserialized_value: Box<dyn Reflect> = reflect_deserializer.deserialize(
&mut ron::Deserializer::from_str(&serialized_value).unwrap()
).unwrap();
// Convert
let converted_value = <MyStruct as FromReflect>::from_reflect(&*deserialized_value).unwrap();
assert_eq!(original_value, converted_value);
Limitations
While this crate offers a lot in terms of adding reflection to Rust, it does come with some limitations that don’t make it as featureful as reflection in other programming languages.
Non-Static Lifetimes
One of the most obvious limitations is the 'static
requirement.
Rust requires fields to define a lifetime for referenced data,
but Reflect
requires all types to have a 'static
lifetime.
This makes it impossible to reflect any type with non-static borrowed data.
Function Reflection
Another limitation is the inability to fully reflect functions and methods. Most languages offer some way of calling methods dynamically, but Rust makes this very difficult to do. For non-generic methods, this can be done by registering custom type data that contains function pointers. For generic methods, the same can be done but will typically require manual monomorphization (i.e. manually specifying the types the generic method can take).
Manual Registration
Since Rust doesn’t provide built-in support for running initialization code before main
,
there is no way for bevy_reflect
to automatically register types into the type registry.
This means types must manually be registered, including their desired monomorphized
representations if generic.
Features
bevy
This feature makes it so that the appropriate reflection traits are implemented on all the types
necessary for the Bevy game engine.
enables the optional dependencies: bevy_math
, glam
, and smallvec
.
These dependencies are used by the Bevy game engine and must define their reflection implementations
within this crate due to Rust’s orphan rule.
documentation
Default | Dependencies |
---|---|
❌ | bevy_reflect_derive/documentation |
This feature enables capturing doc comments as strings for items that derive Reflect
.
Documentation information can then be accessed at runtime on the TypeInfo
of that item.
This can be useful for generating documentation for scripting language interop or for displaying tooltips in an editor.
Re-exports
pub use erased_serde;
Modules
- Representation for individual element accesses within a path.
- Helpers for working with Bevy reflection.
Macros
- A macro used to generate a
FromReflect
trait implementation for the given type. - A replacement for
#[derive(Reflect)]
to be used with foreign types which the definitions of cannot be altered. - A macro used to generate reflection trait implementations for the given type.
- A replacement for deriving
TypePath
for use on foreign types.
Structs
- An error originating from an
Access
of an element within a type. - A container for compile-time array info.
- An iterator over an
Array
. - A fixed-size list of reflected values.
- A dynamic representation of an enum.
- A list of reflected values.
- An ordered mapping between reflected values.
- A struct type which allows fields to be added at runtime.
- A tuple which allows fields to be added at runtime.
- A tuple struct which allows fields to be added at runtime.
- A container for compile-time enum info, used by
TypeInfo
. - An iterator over the field values of a struct.
- A container for compile-time list info.
- An iterator over an
List
. - A container for compile-time map info.
- An iterator over the key-value pairs of a
Map
. - The named field of a reflected struct.
- An
Access
combined with anoffset
for more helpful error reporting. - An error that occurs when parsing reflect path strings.
- A pre-parsed path to an element within a type.
- A struct used to deserialize reflected instances of a type.
- Type data that represents the
FromReflect
trait and allows it to be used dynamically. - A struct used to serialize reflected instances of a type.
- A container for compile-time named struct info.
- Type info for struct variants.
- An iterator over the field values of a tuple.
- A container for compile-time tuple info.
- An iterator over the field values of a tuple struct.
- A container for compile-time tuple struct info.
- Type info for tuple variants.
- Provides dynamic access to all methods on
TypePath
. - Runtime storage for type metadata, registered into the
TypeRegistry
. - A registry of reflected types.
- A synchronized wrapper around a
TypeRegistry
. - Type info for unit variants.
- The unnamed field of a reflected tuple or tuple struct.
- A container for compile-time info related to general value types, including primitives.
- An iterator over the fields in the current enum variant.
Enums
- A singular element access within a path. Multiple accesses can be combined into a
ParsedPath
. - The kind of
AccessError
, along with some kind-specific information. - A dynamic representation of an enum variant.
- A zero-sized enumuration of the “kinds” of a reflected type.
- A mutable enumeration of “kinds” of a reflected type.
- An owned enumeration of “kinds” of a reflected type.
- An error returned from a failed path string query.
- An immutable enumeration of “kinds” of a reflected type.
- Compile-time type information for various reflected types.
- A container for compile-time enum variant info.
- Describes the form of an enum variant.
Traits
- A trait used to power array-like operations via reflection.
- Dynamic dispatch for
TypePath
. - A trait used to power enum-like operations via reflection.
- A trait that enables types to be dynamically constructed from reflected data.
- Trait used to generate
TypeData
for trait reflection. - A convenience trait which combines fetching and downcasting of struct fields.
- A trait which allows nested
Reflect
values to be retrieved with path strings. - A convenience trait which combines fetching and downcasting of tuple fields.
- A convenience trait which combines fetching and downcasting of tuple struct fields.
- A trait which allows a type to generate its
TypeRegistration
for registration into theTypeRegistry
. - A trait used to power list-like operations via reflection.
- A trait used to power map-like operations via reflection.
- The core trait of
bevy_reflect
, used for accessing and modifying data dynamically. - Something that can be interpreted as a reflection path in
GetPath
. - A trait used to power struct-like operations via reflection.
- A trait used to power tuple-like operations via reflection.
- A trait used to power tuple struct-like operations via reflection.
- A trait used to type-erase type metadata.
- A static accessor to type paths and names.
- A static accessor to compile-time type information.
Functions
- The default debug formatter for
Array
types. - Returns the
u64
hash of the given array. - Compares two arrays (one concrete and one reflected) to see if they are equal.
- The default debug formatter for
Enum
types. - Returns the
u64
hash of the given enum. - Applies the elements of
b
to the corresponding elements ofa
. - The default debug formatter for
List
types. - Returns the
u64
hash of the given list. - Applies the elements of reflected map
b
to the corresponding elements of mapa
. - The default debug formatter for
Map
types. - The default debug formatter for
Struct
types. - Applies the elements of
b
to the corresponding elements ofa
. - The default debug formatter for
Tuple
types. - The default debug formatter for
TupleStruct
types. - Compares a
TupleStruct
with aReflect
value.
Attribute Macros
- A macro that automatically generates type data for traits, which their implementors can then register.
Derive Macros
- Derives the
FromReflect
trait. - The main derive macro used by
bevy_reflect
for deriving itsReflect
trait. - Derives the
TypePath
trait, providing a stable alternative to [std::any::type_name
].