我有一个具有某些功能的基本类型,包括特征实现:
use std::fmt;
use std::str::FromStr;
pub struct MyIdentifier {
value: String,
}
impl fmt::Display for MyIdentifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
impl FromStr for MyIdentifier {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(MyIdentifier {
value: s.to_string(),
})
}
}
这是一个简化的示例,实际代码会更复杂。
我想介绍两种类型,它们具有与我描述的基本类型相同的字段和行为,例如MyUserIdentifier
和MyGroupIdentifier
。为避免在使用这些错误时,编译器应将它们视为不同的类型。
我不想复制我刚刚编写的整个代码,而是想重用它。对于面向对象的语言,我将使用继承。我将如何为Rust做这件事?
答案 0 :(得分:2)
有几种方法可以解决此类问题。下面的解决方案使用所谓的 newtype 模式,该新类型包含的对象具有统一的特征,而该新类型具有特征实现。
(说明将是内联的,但是如果您想整体查看代码并同时进行测试,请转到playground。)
首先,我们创建一个特征,该特征描述了我们希望从标识符中看到的最小行为。在Rust中,您没有继承,但具有合成,即一个对象可以实现许多可以描述其行为的特征。如果您希望在所有对象中都有一些共同点(可以通过继承来实现),则必须为它们实现相同的特征。
use std::fmt;
trait Identifier {
fn value(&self) -> &str;
}
然后,我们创建一个包含单个值的新类型,该值是受约束以实现我们的Identifier
特性的泛型类型。这种模式的妙处在于它实际上将在最后由编译器进行优化。
struct Id<T: Identifier>(T);
现在我们有了一个具体的类型,我们为其实现Display
特征。由于Id
的内部对象是Identifier
,因此我们可以在其上调用value
方法,因此我们只需实现一次该特征即可。
impl<T> fmt::Display for Id<T>
where
T: Identifier,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.value())
}
}
以下是不同标识符类型及其Identifier
特征实现的定义:
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);
}
Display
很简单,但是我不认为您可以统一FromStr
,如上面的示例所示,很可能不同的标识符具有不同的字段而不仅仅是{{1 }}(公平地说,有些甚至没有value
,毕竟value
特性只需要对象实现称为Identifier
的方法)。并且在语义上value
应该从字符串构造一个新实例。因此,我将为所有类型分别实现FromStr
。
答案 1 :(得分:2)
使用PhantomData
将类型参数添加到Identifier
中。这使您可以“烙印”给定的标识符:
use std::{fmt, marker::PhantomData, str::FromStr};
pub struct Identifier<K> {
value: String,
_kind: PhantomData<K>,
}
impl<K> fmt::Display for Identifier<K> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
impl<K> FromStr for Identifier<K> {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Identifier {
value: s.to_string(),
_kind: PhantomData,
})
}
}
struct User;
struct Group;
fn main() {
let u_id: Identifier<User> = "howdy".parse().unwrap();
let g_id: Identifier<Group> = "howdy".parse().unwrap();
// do_group_thing(&u_id); // Fails
do_group_thing(&g_id);
}
fn do_group_thing(id: &Identifier<Group>) {}
error[E0308]: mismatched types
--> src/main.rs:32:20
|
32 | do_group_thing(&u_id);
| ^^^^^ expected struct `Group`, found struct `User`
|
= note: expected type `&Identifier<Group>`
found type `&Identifier<User>`
不过,以上并不是我本人实际要做的事情。
我想介绍两种具有相同字段和行为的类型
两种类型不应具有相同的行为,它们应具有相同的类型。
我不想复制我刚刚编写的整个代码,而是想重用它
然后只需重用即可。我们将String
和Vec
之类的类型组成我们较大类型的一部分,从而一直在重用它们。这些类型不像String
或Vec
那样起作用,它们只是使用它们。
也许标识符是您域中的原始类型,它应该存在。创建类似User
或Group
的类型,并传递(引用)用户或组。您当然可以添加类型安全性,但这确实要付出一些程序员的代价。