特征的设计者需要考虑两个不同的受众:将实现该特征的程序员和将使用该特征的程序员。这两种受众导致了特质设计的一定程度的紧张:
- 为了让实现者的工作更轻松,特征最好具有绝对最少数量的方法来实现其目的。
- 为了使用户的生活更加方便,提供一系列涵盖该特征可能使用的所有常见方式的变体方法会很有帮助。
这种紧张可以通过包括更广泛的方法来平衡,使用户的生活更轻松,但 为可以从接口上的其他更原始的操作构建的任何方法提供默认实现。
一个简单的例子是 is_empty()的方法 ExactSizeIterator;它有一个依赖于特征方法的默认实现len() :
fn is_empty(&self) -> bool {
self.len() == 0
}
默认实现的存在就是:默认。如果该特征的实现具有更优化的方法来确定迭代器是否为空,则它可以is_empty()
用自己的迭代器替换默认值。
这种方法导致特征定义的数量较少必需的方法,加上大量默认实现的方法。该特征的实现者只需实现前者,并免费获得后者的全部。
这也是 Rust 标准库广泛遵循的一种方法;也许最好的例子是这个 Iterator特质,它有一个必需的方法(next),但包括一整套预先提供的方法(第 10 项),在撰写本文时超过 50 个。
特征方法可以强加特征边界,表明只有当涉及的类型实现特定特征时,该方法才可用。这Iterator
特征还表明,这与默认方法实现结合起来很有用。例如, cloned()迭代器方法有一个特征绑定和一个默认实现:
fn cloned<'a, T: 'a>(self) -> Cloned<Self>
where
Self: Sized + Iterator<Item = &'a T>,
T: Clone,
{
Cloned::new(self)
}
换句话说,该方法仅在底层类型实现 cloned()
时才可用Item
Clone;当它出现时,该实现将自动可用。
关于具有默认实现的特征方法的最终观察是,即使在特征的初始版本发布之后,也可以安全地将新方法添加到特征中。像这样的添加保留 用户和实现者的向后兼容性1的特质。
因此,请遵循标准库的示例,通过添加具有默认实现(以及适当的特征边界)的方法,为实现者提供最小的 API 界面,同时为用户提供方便且全面的 API。
1:即使实现者不幸已经在具体类型中添加了同名的方法,这也是正确的,因为具体方法(称为固有实现)将在特征方法之前使用。可以通过强制转换来显式选择特征方法<Concrete as Trait>::method()
: