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 and PartialReflect traits
At the root of bevy_reflect is the PartialReflect trait.
Its purpose is to allow dynamic introspection of values, following Rust’s type system through a system of subtraits.
Its primary purpose is to allow all implementors to be passed around
as a dyn PartialReflect trait object in one of the following forms:
&dyn PartialReflect&mut dyn PartialReflectBox<dyn PartialReflect>
This allows values of types implementing PartialReflect
to be operated upon completely dynamically (at a small runtime cost).
Building on PartialReflect is the Reflect trait.
PartialReflect is a supertrait of Reflect
so any type implementing Reflect implements PartialReflect by definition.
dyn Reflect trait objects can be used similarly to dyn PartialReflect,
but Reflect is also often used in trait bounds (like T: Reflect).
The distinction between PartialReflect and Reflect is summarized in the following:
PartialReflectis a trait for interacting with values underbevy_reflect’s data model. This means values implementingPartialReflectcan be dynamically constructed and introspected.- The
Reflecttrait, however, ensures that the interface exposed byPartialReflecton types which additionally implementReflectmirrors the structure of a single Rust type. - This means
dyn Reflecttrait objects can be directly downcast to concrete types, wheredyn PartialReflecttrait object cannot. Reflect, since it provides a stronger type-correctness guarantee, is the trait used to interact with the type registry.
§Converting between PartialReflect and Reflect
Since T: Reflect implies T: PartialReflect, conversion from a dyn Reflect to a dyn PartialReflect
trait object (upcasting) is infallible and can be performed with one of the following methods.
Note that these are temporary while the language feature for dyn upcasting coercion is experimental:
PartialReflect::as_partial_reflectfor&dyn PartialReflectPartialReflect::as_partial_reflect_mutfor&mut dyn PartialReflectPartialReflect::into_partial_reflectforBox<dyn PartialReflect>
For conversion in the other direction — downcasting dyn PartialReflect to dyn Reflect —
there are fallible methods:
PartialReflect::try_as_reflectfor&dyn ReflectPartialReflect::try_as_reflect_mutfor&mut dyn ReflectPartialReflect::try_into_reflectforBox<dyn Reflect>
Additionally, FromReflect::from_reflect can be used to convert a dyn PartialReflect to a concrete type
which implements Reflect.
§Implementing Reflect
Implementing Reflect (and PartialReflect) 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:
GetTypeRegistrationTypedStruct,TupleStruct, orEnumdepending on the type
§Requirements
We can implement Reflect on any type that satisfies both of the following conditions:
- The type implements
Any,Send, andSync. For theAnyrequirement to be satisfied, the type itself must have a'staticlifetime. - 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 Reflection Subtraits
Since PartialReflect is meant to cover any and every type, this crate also comes with a few
more traits to accompany PartialReflect and provide more specific interactions.
We refer to these traits as the reflection subtraits since they all have PartialReflect 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 PartialReflect = my_struct.field("foo").unwrap();
assert_eq!(Some(&123), foo.try_downcast_ref::<i32>());Since most data is passed around as dyn PartialReflect or dyn Reflect trait objects,
the PartialReflect trait has methods for going to and from these subtraits.
PartialReflect::reflect_kind, PartialReflect::reflect_ref,
PartialReflect::reflect_mut, and PartialReflect::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 PartialReflect> = Box::new((1, 2, 3));
let my_tuple = my_tuple.reflect_ref().as_tuple().unwrap();
assert_eq!(3, my_tuple.field_len());And to go back to a general-purpose dyn PartialReflect,
we can just use the matching PartialReflect::as_partial_reflect, PartialReflect::as_partial_reflect_mut,
or PartialReflect::into_partial_reflect methods.
§Opaque Types
Some types don’t fall under a particular subtrait.
These types hide their internal structure to reflection, either because it is not possible, difficult, or not useful to reflect its internals. Such types are known as opaque types.
This includes truly opaque types like String or Instant,
but also includes all the primitive types (e.g. bool, usize, etc.)
since they can’t 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().try_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 PartialReflect::to_dynamic method will return a dynamic type for all non-opaque types,
allowing all types to essentially be “cloned” into a dynamic type.
And since dynamic types themselves implement PartialReflect,
we may pass them around just like most other reflected types.
let original: Box<dyn Reflect> = Box::new(MyStruct {
foo: 123
});
// `dynamic` will be a `DynamicStruct` representing a `MyStruct`
let dynamic: Box<dyn PartialReflect> = original.to_dynamic();
assert!(dynamic.represents::<MyStruct>());§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 PartialReflect::apply and PartialReflect::try_apply methods.
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 dynamic: Box<dyn PartialReflect> = original.to_dynamic();
let value = dynamic.try_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 dynamic: Box<dyn PartialReflect> = original.to_dynamic();
let value = <MyStruct as FromReflect>::from_reflect(&*dynamic).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 an PartialReflect 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(core::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 (and not a dyn PartialReflect)
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 dynamic types 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 ReflectDeserializer 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 opaque 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.as_partial_reflect(), ®istry);
let serialized_value: String = ron::to_string(&reflect_serializer).unwrap();
// Deserialize
let reflect_deserializer = ReflectDeserializer::new(®istry);
let deserialized_value: Box<dyn PartialReflect> = 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.
§Generic Function Reflection
Another limitation is the inability to reflect over generic functions directly. It can be done, but will typically require manual monomorphization (i.e. manually specifying the types the generic method can take).
§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.
§functions
| Default | Dependencies |
|---|---|
| ❌ | bevy_reflect_derive/functions |
This feature allows creating a DynamicFunction or DynamicFunctionMut from Rust functions. Dynamic
functions can then be called with valid ArgLists.
For more information, read the [func] module docs.
§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.
§debug
| Default | Dependencies |
|---|---|
| ✅ | debug_stack |
This feature enables useful debug features for reflection.
This includes the debug_stack feature,
which enables capturing the type stack when serializing or deserializing a type
and displaying it in error messages.
§auto_register_inventory/auto_register_static
| Default | Dependencies |
|---|---|
| ✅ | bevy_reflect_derive/auto_register_inventory |
| ❌ | bevy_reflect_derive/auto_register_static |
These features enable automatic registration of types that derive Reflect.
auto_register_inventoryusesinventoryto collect types on supported platforms (Linux, macOS, iOS, FreeBSD, Android, Windows, WebAssembly).auto_register_staticuses platform-independent way to collect types, but requires additional setup and might slow down compilation, so it should only be used on platforms not supported byinventory. See documentation forload_type_registrationsmacro for more info
When this feature is enabled bevy_reflect will automatically collects all types that derive Reflect on app startup,
and TypeRegistry::register_derived_types can be used to register these types at any point in the program.
However, this does not apply to types with generics: their desired monomorphized representations must be registered manually.
Modules§
- access
- Representation for individual element accesses within a path.
- attributes
- Types and functions for creating, manipulating and querying
CustomAttributes. - erased_
serde - github crates-io docs-rs
- prelude
- The reflect prelude.
- serde
- Serde integration for reflected types.
- std_
traits - Module containing the
ReflectDefaulttype. - utility
- Helpers for working with Bevy reflection.
Macros§
- hash_
error - Used to produce an error message when an attempt is made to hash
a
PartialReflectvalue that does not support hashing. - impl_
from_ reflect_ opaque - A macro used to generate a
FromReflecttrait implementation for the given type. - impl_
reflect - A replacement for
#[derive(Reflect)]to be used with foreign types which the definitions of cannot be altered. - impl_
reflect_ opaque - A macro used to generate reflection trait implementations for the given type.
- impl_
type_ path - A replacement for deriving
TypePathfor use on foreign types. - load_
type_ registrations - Collects and loads type registrations when using
auto_register_staticfeature.
Structs§
- Access
Error - An error originating from an
Accessof an element within a type. - Array
Info - A container for compile-time array info.
- Array
Iter - An iterator over an
Array. - Const
Param Info - Type information for a const generic parameter.
- Dynamic
Array - A fixed-size list of reflected values.
- Dynamic
Enum - A dynamic representation of an enum.
- Dynamic
List - A list of reflected values.
- Dynamic
Map - An unordered mapping between reflected values.
- Dynamic
Set - An unordered set of reflected values.
- Dynamic
Struct - A struct type which allows fields to be added at runtime.
- Dynamic
Tuple - A tuple which allows fields to be added at runtime.
- Dynamic
Tuple Struct - A tuple struct which allows fields to be added at runtime.
- Enum
Info - A container for compile-time enum info, used by
TypeInfo. - Field
Iter - An iterator over the field values of a struct.
- Generics
- The generic parameters of a type.
- List
Info - A container for compile-time list info.
- List
Iter - An iterator over an
List. - MapInfo
- A container for compile-time map info.
- Named
Field - The named field of a reflected struct.
- Offset
Access - An
Accesscombined with anoffsetfor more helpful error reporting. - Opaque
Info - A container for compile-time info related to reflection-opaque types, including primitives.
- Parse
Error - An error that occurs when parsing reflect path strings.
- Parsed
Path - A pre-parsed path to an element within a type.
- Reflect
Deserialize - A struct used to deserialize reflected instances of a type.
- Reflect
From Ptr Reflectvalues are commonly used in situations where the actual types of values are not known at runtime. In such situations you might have access to a*const ()pointer that you know implementsReflect, but have no way of turning it into a&dyn Reflect.- Reflect
From Reflect - Type data that represents the
FromReflecttrait and allows it to be used dynamically. - Reflect
Kind Mismatch Error - Caused when a type was expected to be of a certain kind, but was not.
- Reflect
Serialize - A struct used to serialize reflected instances of a type.
- SetInfo
- A container for compile-time set info.
- Struct
Info - A container for compile-time named struct info.
- Struct
Variant Info - Type info for struct variants.
- Tuple
Field Iter - An iterator over the field values of a tuple.
- Tuple
Info - A container for compile-time tuple info.
- Tuple
Struct Field Iter - An iterator over the field values of a tuple struct.
- Tuple
Struct Info - A container for compile-time tuple struct info.
- Tuple
Variant Info - Type info for tuple variants.
- Type
- The base representation of a Rust type.
- Type
Param Info - Type information for a generic type parameter.
- Type
Path Table - Provides dynamic access to all methods on
TypePath. - Type
Registration - Runtime storage for type metadata, registered into the
TypeRegistry. - Type
Registry - A registry of reflected types.
- Type
Registry Arc - A synchronized wrapper around a
TypeRegistry. - Unit
Variant Info - Type info for unit variants.
- Unnamed
Field - The unnamed field of a reflected tuple or tuple struct.
- Variant
Field Iter - An iterator over the fields in the current enum variant.
Enums§
- Access
- A singular element access within a path.
Multiple accesses can be combined into a
ParsedPath. - Access
Error Kind - The kind of
AccessError, along with some kind-specific information. - Apply
Error - A enumeration of all error outcomes that might happen when running
try_apply. - Dynamic
Variant - A dynamic representation of an enum variant.
- FieldId
- A representation of a field’s accessor.
- Generic
Info - An enum representing a generic parameter.
- Reflect
Clone Error - An error that occurs when cloning a type via
PartialReflect::reflect_clone. - Reflect
Kind - An enumeration of the “kinds” of a reflected type.
- Reflect
Mut - A mutable enumeration of “kinds” of a reflected type.
- Reflect
Owned - An owned enumeration of “kinds” of a reflected type.
- Reflect
Path Error - An error returned from a failed path string query.
- Reflect
Ref - An immutable enumeration of “kinds” of a reflected type.
- Type
Info - Compile-time type information for various reflected types.
- Type
Info Error - A
TypeInfo-specific error. - Variant
Field - A field in the current enum variant.
- Variant
Info - A container for compile-time enum variant info.
- Variant
Info Error - A
VariantInfo-specific error. - Variant
Type - Describes the form of an enum variant.
Traits§
- Array
- A trait used to power array-like operations via reflection.
- Dynamic
Type Path - Dynamic dispatch for
TypePath. - Dynamic
Typed - Dynamic dispatch for
Typed. - Enum
- A trait used to power enum-like operations via reflection.
- From
Reflect - A trait that enables types to be dynamically constructed from reflected data.
- From
Type - Trait used to generate
TypeDatafor trait reflection. - GetField
- A convenience trait which combines fetching and downcasting of struct fields.
- GetPath
- A trait which allows nested
Reflectvalues to be retrieved with path strings. - GetTuple
Field - A convenience trait which combines fetching and downcasting of tuple fields.
- GetTuple
Struct Field - A convenience trait which combines fetching and downcasting of tuple struct fields.
- GetType
Registration - A trait which allows a type to generate its
TypeRegistrationfor registration into theTypeRegistry. - Is
- Checks if the current type “is” another type, using a
TypeIdequality comparison. - List
- A trait used to power list-like operations via reflection.
- Map
- A trait used to power map-like operations via reflection.
- Partial
Reflect - The foundational trait of
bevy_reflect, used for accessing and modifying data dynamically. - Reflect
- A core trait of
bevy_reflect, used for downcasting to concrete types. - Reflect
Path - Something that can be interpreted as a reflection path in
GetPath. - Reflect
Remote - Marks a type as a reflectable wrapper for a remote type.
- Reflectable
- A catch-all trait that is bound by the core reflection traits, useful to simplify reflection-based generic type bounds.
- Set
- A trait used to power set-like operations via reflection.
- Struct
- A trait used to power struct-like operations via reflection.
- Tuple
- A trait used to power tuple-like operations via reflection.
- Tuple
Struct - A trait used to power tuple struct-like operations via reflection.
- Type
Data - A trait used to type-erase type metadata.
- Type
Path - A static accessor to type paths and names.
- Typed
- A static accessor to compile-time type information.
Functions§
- array_
apply - Applies the reflected array data to the given array.
- array_
debug - The default debug formatter for
Arraytypes. - array_
hash - Returns the
u64hash of the given array. - array_
partial_ eq - Compares two arrays (one concrete and one reflected) to see if they are equal.
- array_
try_ apply - Tries to apply the reflected array data to the given array and returns a Result.
- enum_
debug - The default debug formatter for
Enumtypes. - enum_
hash - Returns the
u64hash of the given enum. - enum_
partial_ eq - Compares an
Enumwith aPartialReflectvalue. - list_
apply - Applies the elements of
bto the corresponding elements ofa. - list_
debug - The default debug formatter for
Listtypes. - list_
hash - Returns the
u64hash of the given list. - list_
partial_ eq - Compares a
Listwith aReflectvalue. - list_
try_ apply - Tries to apply the elements of
bto the corresponding elements ofaand returns a Result. - map_
apply - Applies the elements of reflected map
bto the corresponding elements of mapa. - map_
debug - The default debug formatter for
Maptypes. - map_
partial_ eq - Compares a
Mapwith aPartialReflectvalue. - map_
try_ apply - Tries to apply the elements of reflected map
bto the corresponding elements of mapaand returns a Result. - set_
apply - Applies the elements of reflected set
bto the corresponding elements of seta. - set_
debug - The default debug formatter for
Settypes. - set_
partial_ eq - Compares a
Setwith aPartialReflectvalue. - set_
try_ apply - Tries to apply the elements of reflected set
bto the corresponding elements of setaand returns a Result. - struct_
debug - The default debug formatter for
Structtypes. - struct_
partial_ eq - Compares a
Structwith aPartialReflectvalue. - tuple_
apply - Applies the elements of
bto the corresponding elements ofa. - tuple_
debug - The default debug formatter for
Tupletypes. - tuple_
partial_ eq - Compares a
Tuplewith aPartialReflectvalue. - tuple_
struct_ debug - The default debug formatter for
TupleStructtypes. - tuple_
struct_ partial_ eq - Compares a
TupleStructwith aPartialReflectvalue. - tuple_
try_ apply - Tries to apply the elements of
bto the corresponding elements ofaand returns a Result.
Attribute Macros§
- reflect_
remote - Generates a wrapper type that can be used to “derive
Reflect” for remote types. - reflect_
trait - A macro that automatically generates type data for traits, which their implementors can then register.
Derive Macros§
- From
Reflect - Derives the
FromReflecttrait. - Reflect
- The main derive macro used by
bevy_reflectfor deriving itsReflecttrait. - Type
Path - Derives the
TypePathtrait, providing a stable alternative to [std::any::type_name].