定义特型,类型实现特型,类型上调用特型方法,这是常见的调用方法.
看下面的代码会不会有些别扭,重点看ScorerBuilder::label(builder).
pub trait ScorerBuilder: std::fmt::Debug + Sync + Send {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity);
fn label(&self) -> Option<&str> {
None
}
}
pub fn spawn_scorer<T: ScorerBuilder + ?Sized>(
builder: &T,
cmd: &mut Commands,
actor: Entity,
) -> Entity {
let scorer_ent = cmd.spawn_empty().id();
let span = ScorerSpan::new(scorer_ent, ScorerBuilder::label(builder));
let _guard = span.span().enter();
debug!("New Scorer spawned.");
cmd.entity(scorer_ent)
.insert(Name::new("Scorer"))
.insert(Score::default())
.insert(Actor(actor));
builder.build(cmd, scorer_ent, actor);
std::mem::drop(_guard);
cmd.entity(scorer_ent).insert(span);
scorer_ent
}
特型方法的签名是fn label(&self) -> Option<&str>,
所以静态方法调用ScorerBuilder::label(builder)是没问题的.
那么问题来了,builder.label()明显更加简洁,不这么写的好处在哪里?
一个例子说明
trait ScorerBuilder {
fn label(&self) -> Option<&str> {
None
}
}
struct MyBuilder {
custom_label: String,
}
impl ScorerBuilder for MyBuilder {
fn label(&self) -> Option<&str> {
Some("ScorerBuilder label")
}
}
impl MyBuilder {
fn label(&self) -> &str {
&self.custom_label
}
}
fn main() {
let builder = MyBuilder {
custom_label: "Custom label".to_string(),
};
// 调用类型自身的 label 方法
println!("{}", builder.label());
// 调用 ScorerBuilder 特征中的 label 方法
println!("{:?}", ScorerBuilder::label(&builder));
}
在 Rust 中结构体的普通方法和 trait 的方法是允许同名的,通过调用方式区分.
写到这儿肯定是不痛不痒的,看下面的汇总.
方法调用的6种姿势
| 编号 | 写法 | 类型 | 是否限定 | 示例 | 说明 |
|---|---|---|---|---|---|
| ① | value.method() | 结构体方法,推导 trait | 非限定 | builder.label() | 编译器自动查找,优先类型方法,其次唯一 trait 方法 |
| ② | Trait::method(&value) | Trait 名称 | 限定(省略类型) | ScorerBuilder::label(&builder) | 指定 trait,明确不是普通方法 |
| ③ | <Type>::method(&value) |
明确类型 | 限定(省略 trait) | ::label(&builder) | 明确告知类型,trait 可推导 |
| ④ | <_ as Trait>::method(&value) |
类型由上下文推导 | 限定(省略类型) | <_ as ScorerBuilder>::label(&builder) | 类型实现了多个特型,但只有一个特型有label方法,让编译器自己推导 |
| ⑤ | <Type as Trait>::method(&value) |
最完整 | 限定(无省略) | ::label(&builder) | 完全限定,编译期别推导了,直接告诉你 |
| ⑥ | trait_object.method() | 通过 trait 对象 | 动态分发 | let obj: &dyn ScorerBuilder = &builder; obj.label(); | 动态调用 trait 方法,支持运行时多态(vtable) |
其中1是非限定调用,6是多态的动态分发写法,2-5就是4种完全限定变体写法,书上对这4种有单独章节来描述.
再次梳理一下:<Type as Trait>::method()是完全限定,视情况可以省略:
- 编译期无法推导出type时,要明确带上Type.
- eg: 特型的关联函数(参数中没有self).
- 无类型可转换成多种合适类型(32.xx()是i32还是u32).
- 泛型函数中调用trait方法,此时编译器不一定能推导出类型的
- 编译期无法推导出Trait时,需要明确带上Trait.
- 类型实现了多个trait都有同一个方法,编译期不知道要调用哪个
- 普通的value.method()是有特殊处理的,如果没有普通方法,就尝试调用特型方法.
as写法用于类型派生的多个特型有同名方法时,各有个的用法.
最后回答一下上面的问题: 静态调用的好处是什么?
明确调用逻辑.
如果没有重名,value.method()最简洁; 如果有重名,用完全限定变体写法.