有几种方法可以处理这种问题。以下解决方案使用所谓的“newtype”模式,即新类型包含的对象的统一特征和新类型的特征实现。
(说明将在行内进行,但如果您想同时查看整个代码并测试它,请转到
playground。)
首先,我们创建一个描述标识符所需最小行为的特征。在Rust中,您没有继承,而是组合,即一个对象可以实现任意数量的特征来描述其行为。如果您想拥有所有对象中都共有的东西——这是通过继承实现的——那么您必须为它们实现相同的特征。
use std::fmt;
trait Identifier {
fn value(&self) -> &str;
}
然后,我们创建一个新类型,其中包含一个单一值,该值是一个泛型类型,受限于实现我们的
Identifier
trait。这种模式的好处在于编译器最终会进行优化。
struct Id<T: Identifier>(T);
现在我们有了一个具体的类型,我们要为它实现Display trait。由于Id的内部对象是一个Identifier,因此我们可以调用它的value方法,这样我们只需一次实现这个trait。
impl<T> fmt::Display for Id<T>
where
T: Identifier,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.value())
}
}
以下是不同标识符类型及其“标识符”特征实现的定义:
struct MyIdentifier(String);
impl Identifier for MyIdentifier {
fn value(&self) -> &str {
self.0.as_str()
}
}
struct MyUserIdentifier {
value: String,
user: String,
}
impl Identifier for MyUserIdentifier {
fn value(&self) -> &str {
self.value.as_str()
}
}
最后但并非最不重要的,这就是你如何使用它们:
fn main() {
let mid = Id(MyIdentifier("Hello".to_string()));
let uid = Id(MyUserIdentifier {
value: "World".to_string(),
user: "Cybran".to_string(),
});
println!("{}", mid);
println!("{}", uid);
}
显示器很容易,但我认为你不能统一 FromStr。就像我上面的示例所演示的那样,不同的标识符很可能有不同的字段,而不仅仅是 value(公平地说,有些甚至没有 value,在所有情况下,Identifier 特性只要求对象实现一个名为 value 的方法)。从语义上讲,FromStr 应该从字符串构造一个新的实例。因此,我会为所有类型单独实现 FromStr。