derive(Reflect) 入门
给一个结构体加反射能力,只需要一行 derive:
#[derive(Reflect)]
struct Player {
name: String,
hp: i32,
position: Vec3,
}#[derive(Reflect)] 自动为 Player 实现了 PartialReflect 和 Reflect 两个 trait,以及 Struct 子 trait(因为它是命名字段结构体)。从此这个类型在运行时有了"自我描述"的能力——你能拿到它的字段数量、每个字段的名字和值,全部通过 trait 方法完成。
derive(Reflect) 要求每个字段的类型也实现了 Reflect。标准库的常见类型(i32、f32、String、Vec<T>、Option<T>、HashMap<K,V> 等)都已经实现;Bevy 的 Vec3、Color、Transform 等引擎类型也一样。如果某个字段确实不需要反射,可以用 #[reflect(ignore)] 跳过它。
枚举字段
derive(Reflect) 也适用于枚举。生成的是 Enum 子 trait,允许你在运行时判断当前是哪个 variant、访问 variant 的字段:
#[derive(Reflect)]
enum AiState {
Idle,
Chase { target: Entity },
Attack(f32),
}运行时通过 reflect_ref() 匹配 ReflectRef::Enum 就能拿到 variant 名和字段。
列举字段
拿到一个 Reflect 值后,通过 reflect_ref() 返回的枚举来判断它的"种类"——是结构体、元组结构体、枚举、列表、Map 还是不透明类型:
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:?}");
}
}运行:
cargo run -p ch31-reflect --example listing-31-01字段数量: 3
name = "Alice"
hp = 100
position = Vec3(1.0, 2.0, 3.0)
weapon 是 Struct: trueReflectKind 枚举列出了所有种类:Struct、TupleStruct、Enum、List、Map、Set、Array、Opaque。u32、String 这类"叶子"类型属于 Opaque——你知道它是什么类型,但不需要(也不能)深入到内部字段。
#[reflect(...)] 属性
derive 宏支持用 #[reflect(...)] 注册 TypeData——给类型附加额外的运行时元信息。最常见的两个:
#[reflect(Default)]:让类型在运行时能通过ReflectDefault调用Default::default()创建默认值#[reflect(Component)]:让 Scene 系统知道这个类型是组件,可以动态插入/移除实体
#[derive(Reflect, Default)]
#[reflect(Default)]
struct Weapon {
damage: f32,
name: String,
}#[reflect(Default)] 前提是类型确实实现了 Default。这在后面的 TypeRegistry 一节会详细展开——先记住这个模式:derive + reflect 属性 = 编译期注册,运行时可用。
PartialReflect vs Reflect
这两个 trait 是父子关系:
PartialReflect:基础反射能力——apply、reflect_ref、reflect_mut、to_dynamic、get_represented_type_info。大多数操作只需要它Reflect(子 trait):额外提供into_any/as_any/as_any_mut,支持向下转型到具体类型
derive(Reflect) 同时实现两者。日常使用几乎不需要区分——但在存 Box<dyn ...> 时要注意:Box<dyn PartialReflect> 可以持有动态类型(如 DynamicStruct),而 Box<dyn Reflect 一定是具体类型。
下一节看 TypeRegistry 怎么把这些编译期注册的类型信息收集起来,供运行时按名字查找。