特征
假如说方法定义了类型的行为,那么特征(trait)则总结了一组类型所共有的行为。在定义一个全新的 特征时,通常需要编写两个部分:
- 特征定义:使用
trait关键字定义,它描述了特征所包含的方法签名。 - 特征实现:使用
impl关键字为特定类型定义特征的实现。
在示例中,我们定义了一个Equal特征,它总结了一个is_equal方法:接受两个Self类型的参数,
Self类型表示实现该特征的具体类型;方法返回一个布尔值,在这里用来表示Self类型的两个值
是否相等。
我们分别为内建的Int类型和自己定义的Pos类型实现了Equal特征,提供了is_equal方法的具体实现。
因为特征定义已经声明过is_equal方法的类型,所以在实现中可以省略参数和返回值的类型标注。
在main函数中,通过Equal::is_equal()可以调用该特征总结的方法,根据传入的a、b类型,不同类型
会调用各自的实现版本。也就是说:
- 当
a和b是Int类型时,执行的是impl Int for Equal with is_equal内的代码。 - 当
a和b是Pos类型时,执行的是impl Pos for Equal with is_equal内的代码。
调用哪个实现版本是静态地决定的,没有运行时动态分派的开销。
限制
在为类型实现特征时,MoonBit 遵循孤儿规则(orphan rule):impl Type for Trait ...实现必须和Type或Trait在同一个包,
而不允许孤立地存在于某个包中。
这一限制保证了特征实现的唯一性,避免了不同包中对同一类型和特征的重复实现引发冲突,或者因为其他包的变化而改变已有代码的行为。
用途
其实,在 MoonBit 的标准库的moonbitlang/core/builtin包中就定义了这样一个特征,名为Eq,
它总结了一个equal方法,用于比较两个值是否相等。并且标准库已经为许多内建类型实现了Eq特征,
比如Int、Bool、Array等。标准库还提供了Show特征,用于将值转换为字符串表示。
我们后续将会介绍这些特征的使用场景。
你可能会疑惑为什么要使用trait, 而不是直接分别为类型定义is_equal方法。
下一节我们将介绍特征的一个重要用途:特征约束。
///|
trait Equal {
is_equal(Self, Self) -> Bool
}
///|
impl Equal for Int with is_equal(a, b) {
a == b
}
///|
struct Pos(Int, Int)
///|
impl Equal for Pos with is_equal(a, b) {
let Pos(x1, y1) = a
let Pos(x2, y2) = b
x1 == x2 && y1 == y2
}
///|
fn main {
println(Equal::is_equal(1, 1))
println(Equal::is_equal(1, 2))
let pos1 = Pos(1, 2)
let pos2 = Pos(1, 2)
let pos3 = Pos(4, 5)
println(Equal::is_equal(pos1, pos2))
println(Equal::is_equal(pos2, pos3))
}