使用默认实现来最小化所需的特征方法

特征的设计者需要考虑两种不同的受众:将要实现特征的程序员和将要使用特征的程序员。这两种受众导致特征设计中存在一定程度的紧张关系:

为了使实现者的工作更轻松,特征最好具有实现其目的的绝对最少方法数。

为了使用户的工作更方便,提供一系列涵盖特征可能使用的所有常见方式的变体方法会很有帮助。 可以通过包括使用户的工作更轻松的更广泛的方法来平衡这种紧张关系,但为可以从接口上的其他更原始的操作构建的任何方法提供默认实现。

一个简单的例子是 ExactSizeIteratoris_empty() 方法,它是一个知道它正在迭代多少事物的迭代器。此方法有一个依赖于 len() 特征方法的默认实现:

#![allow(unused)]
fn main() {
fn is_empty(&self) -> bool {
    self.len() == 0
}
}

默认实现的存在就是:默认。如果特征的实现有不同的方式来确定迭代器是否为空,它可以用自己的方法替换默认的 is_empty()

这种方法导致特征定义具有少量必需方法,以及大量默认实现的方法。特征的实现者只需实现前者,即可免费获得所有后者。

这也是 Rust 标准库广泛遵循的一种方法;也许最好的例子是 Iterator 特征,它只有一个必需方法(next()),但包含大量预先提供的方法(条目 9),在撰写本文时超过 50 种。

特征方法可以施加特征界限,表明只有当涉及的类型实现特定特征时,方法才可用。 Iterator 特征还表明这与默认方法实现结合使用很有用。例如, cloned() 迭代器方法具有特征界限和默认实现:

#![allow(unused)]
fn main() {
fn cloned<'a, T>(self) -> Cloned<Self>
where
    T: 'a + Clone,
    Self: Sized + Iterator<Item = &'a T>,
{
    Cloned::new(self)
}
}

换句话说,只有当底层 Item 类型实现 Clone 时, cloned() 方法才可用;当实现 Clone 时,实现将自动可用。

关于具有默认实现的特征方法的最终观察是,即使在特征的初始版本发布后,通常也可以安全地将新方法添加到特征中。只要新方法名称不与该类型实现的其他特征的方法名称冲突,这样的添加就可以为特征的用户和实现者保留向后兼容性(参见 Item 21)。

因此,请遵循标准库的示例,通过添加具有默认实现的方法(以及适当的特征界限),为实现者提供最小的 API 接口,但为用户提供方便而全面的 API。