Skip to content

derive(Reflect) 入门

给一个结构体加反射能力,只需要一行 derive:

rust
#[derive(Reflect)]
struct Player {
    name: String,
    hp: i32,
    position: Vec3,
}

#[derive(Reflect)] 自动为 Player 实现了 PartialReflectReflect 两个 trait,以及 Struct 子 trait(因为它是命名字段结构体)。从此这个类型在运行时有了"自我描述"的能力——你能拿到它的字段数量、每个字段的名字和值,全部通过 trait 方法完成。

derive(Reflect) 要求每个字段的类型也实现了 Reflect。标准库的常见类型(i32f32StringVec<T>Option<T>HashMap<K,V> 等)都已经实现;Bevy 的 Vec3ColorTransform 等引擎类型也一样。如果某个字段确实不需要反射,可以用 #[reflect(ignore)] 跳过它。

枚举字段

derive(Reflect) 也适用于枚举。生成的是 Enum 子 trait,允许你在运行时判断当前是哪个 variant、访问 variant 的字段:

rust
#[derive(Reflect)]
enum AiState {
    Idle,
    Chase { target: Entity },
    Attack(f32),
}

运行时通过 reflect_ref() 匹配 ReflectRef::Enum 就能拿到 variant 名和字段。

列举字段

拿到一个 Reflect 值后,通过 reflect_ref() 返回的枚举来判断它的"种类"——是结构体、元组结构体、枚举、列表、Map 还是不透明类型:

rust
if let ReflectRef::Struct(s) = player.reflect_ref() {
        println!("字段数量: {}", s.field_len());
        for (i, value) in s.iter_fields().enumerate() {
            let name = s.name_at(i).unwrap();
            println!("  {name} = {value:?}");
        }
    }

运行:

console
cargo run -p ch31-reflect --example listing-31-01
text
字段数量: 3
  name = "Alice"
  hp = 100
  position = Vec3(1.0, 2.0, 3.0)
weapon 是 Struct: true

ReflectKind 枚举列出了所有种类:StructTupleStructEnumListMapSetArrayOpaqueu32String 这类"叶子"类型属于 Opaque——你知道它是什么类型,但不需要(也不能)深入到内部字段。

#[reflect(...)] 属性

derive 宏支持用 #[reflect(...)] 注册 TypeData——给类型附加额外的运行时元信息。最常见的两个:

  • #[reflect(Default)]:让类型在运行时能通过 ReflectDefault 调用 Default::default() 创建默认值
  • #[reflect(Component)]:让 Scene 系统知道这个类型是组件,可以动态插入/移除实体
rust
#[derive(Reflect, Default)]
#[reflect(Default)]
struct Weapon {
    damage: f32,
    name: String,
}

#[reflect(Default)] 前提是类型确实实现了 Default。这在后面的 TypeRegistry 一节会详细展开——先记住这个模式:derive + reflect 属性 = 编译期注册,运行时可用

PartialReflect vs Reflect

这两个 trait 是父子关系:

  • PartialReflect:基础反射能力——applyreflect_refreflect_mutto_dynamicget_represented_type_info。大多数操作只需要它
  • Reflect(子 trait):额外提供 into_any/as_any/as_any_mut,支持向下转型到具体类型

derive(Reflect) 同时实现两者。日常使用几乎不需要区分——但在存 Box<dyn ...> 时要注意:Box<dyn PartialReflect> 可以持有动态类型(如 DynamicStruct),而 Box<dyn Reflect 一定是具体类型。

下一节看 TypeRegistry 怎么把这些编译期注册的类型信息收集起来,供运行时按名字查找。