bevy/crates/bevy_reflect
Zachary Harrold cc69fdd0c6
Add no_std support to bevy (#17955)
# Objective

- Fixes #15460 (will open new issues for further `no_std` efforts)
- Supersedes #17715

## Solution

- Threaded in new features as required
- Made certain crates optional but default enabled
- Removed `compile-check-no-std` from internal `ci` tool since GitHub CI
can now simply check `bevy` itself now
- Added CI task to check `bevy` on `thumbv6m-none-eabi` to ensure
`portable-atomic` support is still valid [^1]

[^1]: This may be controversial, since it could be interpreted as
implying Bevy will maintain support for `thumbv6m-none-eabi` going
forward. In reality, just like `x86_64-unknown-none`, this is a
[canary](https://en.wiktionary.org/wiki/canary_in_a_coal_mine) target to
make it clear when `portable-atomic` no longer works as intended (fixing
atomic support on atomically challenged platforms). If a PR comes
through and makes supporting this class of platforms impossible, then
this CI task can be removed. I however wager this won't be a problem.

## Testing

- CI

---

## Release Notes

Bevy now has support for `no_std` directly from the `bevy` crate.

Users can disable default features and enable a new `default_no_std`
feature instead, allowing `bevy` to be used in `no_std` applications and
libraries.

```toml
# Bevy for `no_std` platforms
bevy = { version = "0.16", default-features = false, features = ["default_no_std"] }
```

`default_no_std` enables certain required features, such as `libm` and
`critical-section`, and as many optional crates as possible (currently
just `bevy_state`). For atomically-challenged platforms such as the
Raspberry Pi Pico, `portable-atomic` will be used automatically.

For library authors, we recommend depending on `bevy` with
`default-features = false` to allow `std` and `no_std` users to both
depend on your crate. Here are some recommended features a library crate
may want to expose:

```toml
[features]
# Most users will be on a platform which has `std` and can use the more-powerful `async_executor`.
default = ["std", "async_executor"]

# Features for typical platforms.
std = ["bevy/std"]
async_executor = ["bevy/async_executor"]

# Features for `no_std` platforms.
libm = ["bevy/libm"]
critical-section = ["bevy/critical-section"]

[dependencies]
# We disable default features to ensure we don't accidentally enable `std` on `no_std` targets, for example. 
bevy = { version = "0.16", default-features = false }
```

While this is verbose, it gives the maximum control to end-users to
decide how they wish to use Bevy on their platform.

We encourage library authors to experiment with `no_std` support. For
libraries relying exclusively on `bevy` and no other dependencies, it
may be as simple as adding `#![no_std]` to your `lib.rs` and exposing
features as above! Bevy can also provide many `std` types, such as
`HashMap`, `Mutex`, and `Instant` on all platforms. See
`bevy::platform_support` for details on what's available out of the box!

## Migration Guide

- If you were previously relying on `bevy` with default features
disabled, you may need to enable the `std` and `async_executor`
features.
- `bevy_reflect` has had its `bevy` feature removed. If you were relying
on this feature, simply enable `smallvec` and `smol_str` instead.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-03-07 03:39:46 +00:00
..
compile_fail Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
derive Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
examples
src Improve bevy_reflect no_std support (#18060) 2025-02-27 06:16:10 +00:00
Cargo.toml Add no_std support to bevy (#17955) 2025-03-07 03:39:46 +00:00
LICENSE-APACHE Cleanup publish process (#17728) 2025-02-09 17:46:19 +00:00
LICENSE-MIT Cleanup publish process (#17728) 2025-02-09 17:46:19 +00:00
README.md reflect: implement the unique reflect rfc (#7207) 2024-08-12 17:01:41 +00:00

Bevy Reflect

License Crates.io Downloads Docs Discord

This crate enables you to dynamically interact with Rust types:

  • Derive the Reflect traits
  • Interact with fields using their names (for named structs) or indices (for tuple structs)
  • "Patch" your types with new values
  • Look up nested fields using "path strings"
  • Iterate over struct fields
  • Automatically serialize and deserialize via Serde (without explicit serde impls)
  • Trait "reflection"

Features

Derive the Reflect traits

// this will automatically implement the `Reflect` trait and the `Struct` trait (because the type is a struct)
#[derive(Reflect)]
struct Foo {
    a: u32,
    b: Bar,
    c: Vec<i32>,
    d: Vec<Baz>,
}

// this will automatically implement the `Reflect` trait and the `TupleStruct` trait (because the type is a tuple struct)
#[derive(Reflect)]
struct Bar(String);

#[derive(Reflect)]
struct Baz {
    value: f32,
}

// We will use this value to illustrate `bevy_reflect` features
let mut foo = Foo {
    a: 1,
    b: Bar("hello".to_string()),
    c: vec![1, 2],
    d: vec![Baz { value: 3.14 }],
};

Interact with fields using their names

assert_eq!(*foo.get_field::<u32>("a").unwrap(), 1);

*foo.get_field_mut::<u32>("a").unwrap() = 2;

assert_eq!(foo.a, 2);

"Patch" your types with new values

let mut dynamic_struct = DynamicStruct::default();
dynamic_struct.insert("a", 42u32);
dynamic_struct.insert("c", vec![3, 4, 5]);

foo.apply(&dynamic_struct);

assert_eq!(foo.a, 42);
assert_eq!(foo.c, vec![3, 4, 5]);

Look up nested fields using "path strings"

let value = *foo.get_path::<f32>("d[0].value").unwrap();
assert_eq!(value, 3.14);

Iterate over struct fields

for (i, value: &Reflect) in foo.iter_fields().enumerate() {
    let field_name = foo.name_at(i).unwrap();
    if let Some(value) = value.downcast_ref::<u32>() {
        println!("{} is a u32 with the value: {}", field_name, *value);
    }
}

Automatically serialize and deserialize via Serde (without explicit serde impls)

let mut registry = TypeRegistry::default();
registry.register::<u32>();
registry.register::<i32>();
registry.register::<f32>();
registry.register::<String>();
registry.register::<Bar>();
registry.register::<Baz>();

let serializer = ReflectSerializer::new(&foo, &registry);
let serialized = ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();

let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap();
let reflect_deserializer = ReflectDeserializer::new(&registry);
let value = reflect_deserializer.deserialize(&mut deserializer).unwrap();
let dynamic_struct = value.take::<DynamicStruct>().unwrap();

assert!(foo.reflect_partial_eq(&dynamic_struct).unwrap());

Trait "reflection"

Call a trait on a given &dyn Reflect reference without knowing the underlying type!

#[derive(Reflect)]
#[reflect(DoThing)]
struct MyType {
    value: String,
}

impl DoThing for MyType {
    fn do_thing(&self) -> String {
        format!("{} World!", self.value)
    }
}

#[reflect_trait]
pub trait DoThing {
    fn do_thing(&self) -> String;
}

// First, lets box our type as a Box<dyn Reflect>
let reflect_value: Box<dyn Reflect> = Box::new(MyType {
    value: "Hello".to_string(),
});

// This means we no longer have direct access to MyType or its methods. We can only call Reflect methods on reflect_value.
// What if we want to call `do_thing` on our type? We could downcast using reflect_value.downcast_ref::<MyType>(), but what if we
// don't know the type at compile time?

// Normally in rust we would be out of luck at this point. Lets use our new reflection powers to do something cool!
let mut type_registry = TypeRegistry::default();
type_registry.register::<MyType>();

// The #[reflect] attribute we put on our DoThing trait generated a new `ReflectDoThing` struct, which implements TypeData.
// This was added to MyType's TypeRegistration.
let reflect_do_thing = type_registry
    .get_type_data::<ReflectDoThing>(reflect_value.type_id())
    .unwrap();

// We can use this generated type to convert our `&dyn Reflect` reference to a `&dyn DoThing` reference
let my_trait: &dyn DoThing = reflect_do_thing.get(&*reflect_value).unwrap();

// Which means we can now call do_thing(). Magic!
println!("{}", my_trait.do_thing());

// This works because the #[reflect(MyTrait)] we put on MyType informed the Reflect derive to insert a new instance
// of ReflectDoThing into MyType's registration. The instance knows how to cast &dyn Reflect to &dyn DoThing, because it
// knows that &dyn Reflect should first be downcasted to &MyType, which can then be safely casted to &dyn DoThing

Why make this?

The whole point of Rust is static safety! Why build something that makes it easy to throw it all away?

  • Some problems are inherently dynamic (scripting, some types of serialization / deserialization)
  • Sometimes the dynamic way is easier
  • Sometimes the dynamic way puts less burden on your users to derive a bunch of traits (this was a big motivator for the Bevy project)