静态方法调用还有隐藏的部分

定义特型,类型实现特型,类型上调用特型方法,这是常见的调用方法.

看下面的代码会不会有些别扭,重点看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 的方法是允许同名的,通过调用方式区分.

写到这儿肯定是不痛不痒的,看下面的汇总.

编号 写法 类型 是否限定 示例 说明
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()最简洁; 如果有重名,用完全限定变体写法.