为何Rust函数可满足Bevy的IntoSystemDescriptor<Params> trait?
问题
我有以下几个Rust函数:
fn add_people(mut commands: Commands) { commands.spawn((Person, Name("A".to_string()))); commands.spawn((Person, Name("B".to_string()))); commands.spawn((Person, Name("C".to_string()))); commands.spawn((Person, Name("D".to_string()))); } fn greet_people(query: Query<&Name, With<Person>>) { for name in query.iter() { println!("hello {}!", name.0); } } fn hello_world() { println!("hello world"); }
这些函数的类型分别为fn(Commands)、fn(Query<&Name, With<Person>>)、fn(),在Bevy框架中可直接传入add_startup_system和add_system方法(方法定义如下):
pub fn add_startup_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) pub fn add_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>)
示例调用代码:
App::new() .add_plugins(DefaultPlugins) .add_startup_system(hello_world) .add_startup_system(add_people) .add_system(greet_people) .run();
IntoSystemDescriptor<Params> trait的定义如下:
pub trait IntoSystemDescriptor<Params> { fn into_descriptor(self) -> SystemDescriptor; /// Assigns a run criteria to the system. Can be a new descriptor or a label of a /// run criteria defined elsewhere. fn with_run_criteria<Marker>( self, run_criteria: impl IntoRunCriteria<Marker>, ) -> SystemDescriptor; /// Assigns a label to the system; there can be more than one, and it doesn't have to be unique. fn label(self, label: impl SystemLabel) -> SystemDescriptor; /// Specifies that the system should run before systems with the given label. fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor; /// Specifies that the system should run after systems with the given label. fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor; /// Marks this system as ambiguous with any system with the specified label. /// This means that execution order between these systems does not matter, /// which allows [some warnings](crate::schedule::ReportExecutionOrderAmbiguities) to be silenced. fn ambiguous_with<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor; /// Specifies that this system should opt out of /// [execution order ambiguity detection](crate::schedule::ReportExecutionOrderAmbiguities). fn ignore_all_ambiguities(self) -> SystemDescriptor; /// Specifies that the system should run with other exclusive systems at the start of stage. fn at_start(self) -> SystemDescriptor; /// Specifies that the system should run with other exclusive systems after the parallel /// systems and before command buffer application. fn before_commands(self) -> SystemDescriptor; /// Specifies that the system should run with other exclusive systems at the end of stage. fn at_end(self) -> SystemDescriptor; }
我无法理解为何这些函数类型会实现IntoSystemDescriptor<Params> trait,二者看起来并无关联,希望有人解释原因,或提供相关Rust特性的说明。
回答
核心原因是Rust的**Blanket Implementations(通用blanket实现)**特性,Bevy框架借助这个特性,为所有符合条件的函数类型自动实现了IntoSystemDescriptor<Params> trait。
具体细节:
- Bevy内部定义了一个泛型trait实现,针对所有满足「函数参数符合系统参数规范」的函数类型,大致结构如下:
impl<F, Params> IntoSystemDescriptor<Params> for F where F: IntoSystem<Params, ()>, // IntoSystem是Bevy的核心trait,负责将普通函数转换为可调度的系统 { fn into_descriptor(self) -> SystemDescriptor { // 内部逻辑:将函数包装为框架可识别的SystemDescriptor实例 } // 其他trait方法的默认实现... } - 你写的三个函数都满足
IntoSystem<Params, ()>的约束:Bevy会自动解析函数参数(比如Commands、Query都是框架支持的系统参数类型),把普通函数转换成可被调度的系统对象,进而自动实现IntoSystemDescriptor。
简单来说,Bevy通过Rust的泛型和trait系统,完成了普通函数到框架系统描述符的自动适配,不需要你手动为每个函数编写trait实现,这就是你能直接把函数传给add_startup_system和add_system的原因。
内容的提问来源于stack exchange,提问作者alice pro
相关产品推荐
相关产品推荐

