如何处理特质类型?
How to deal with trait types?
我来自 JVM 世界,我正在努力处理这些特征。我的目标是提供一个代表时间提供者的接口(在 Java 中将被调用),因此我可以在生产环境中使用 'real' 实现,在测试中使用 'fake' , 这样我就可以打发时间了。
在Java我会
interface Clock {
Instant now();
}
class UtcClock implements Clock {
Instant now() {
return Instant.now();
}
}
然后我可以像使用任何其他类型一样使用 Clock
类型。
我有 Rust
pub trait Clock {
fn now(&self) -> DateTime<Utc>;
}
pub struct UtcClock;
impl Clock for UtcClock {
fn now(&self) -> DateTime<Utc> {
return Utc::now();
}
}
然而,为了能够在 Rust 中使用动态类型 Clock
并在线程之间移动它,我必须使用盒装类型,例如Arc<dyn Clock + Send + Sync>
如果我使用具体类型 UtcClock
,则不需要。
是否提供了在 Rust 中使用 traits 惯用的解决方案?或者还有其他技术可以解耦 'interface' 和实现?
如果可以,那么有什么方法可以让它看起来比 Arc<dyn Clock + Send + Sync>
更好吗?
Arc<dyn Clock + Send + Sync>
似乎是最好的,除非您能够使用 &dyn Clock + Send + Sync
。然后,您可以使用别名类型使其看起来更好,例如type DynClock = Arc<dyn Clock + Send + Sync>;
。参见 the Rust book and the reference。
您不必使用特征对象(即某种 dyn Clock
),您也可以使用常规泛型:
fn use_clock<T: Clock>(clock: T) {
let time = clock.now();
// ...
}
虽然我想你会有一个包含各种“服务”的结构,并且到处都有很多泛型可能会有点混乱。
至于特征对象,如果你只是想“不必考虑它”并获得 Java-like 行为,Arc<dyn Clock>
将在很大程度上做你想做的,除了 thread-safety 相关特征。要解决这些问题,您可以编写 Arc<dyn Clock + Send + Sync + 'static>
,但我更喜欢制作这些超级特性:
trait Clock: Send + Sync + 'static /* + whatever other traits you want */ {
// ...
}
请注意,此语法 不会像 Java 中的等效语法那样进行继承。相反,它施加了以下限制:实现 Clock
的 类型也必须实现 Send
,等等 。特别是在使用单元结构(隐式 Send + Sync + 'static
)时,将约束移至特征定义有助于减少样板文件。
还有类型别名,它基本上允许您更改类型的“拼写”:
type ArcClock = Arc<dyn Clock + Send + Sync + 'static>;
然后你可以像普通类型一样使用 ArcClock
。请注意,对于编译器而言,ArcClock
与 Arc<dyn Clock + ...>
.
字面意思相同
顺便说一句,在 Rust 中,trait 对象经常因为“性能差”而被看不起。虽然在 trait 对象上调用方法通常比常规的单态泛型更难优化并且需要额外的指针间接寻址,但除非您在紧密循环中调用该方法,否则这种开销可能相当小。
例如,对于存储用户创建时间的网络服务器,特征对象的开销可以忽略不计。 FWIW,所有 java 方法调用都以这种方式运行,因此在最坏的情况下您将获得 Java-tier 性能。
我来自 JVM 世界,我正在努力处理这些特征。我的目标是提供一个代表时间提供者的接口(在 Java 中将被调用),因此我可以在生产环境中使用 'real' 实现,在测试中使用 'fake' , 这样我就可以打发时间了。
在Java我会
interface Clock {
Instant now();
}
class UtcClock implements Clock {
Instant now() {
return Instant.now();
}
}
然后我可以像使用任何其他类型一样使用 Clock
类型。
我有 Rust
pub trait Clock {
fn now(&self) -> DateTime<Utc>;
}
pub struct UtcClock;
impl Clock for UtcClock {
fn now(&self) -> DateTime<Utc> {
return Utc::now();
}
}
然而,为了能够在 Rust 中使用动态类型 Clock
并在线程之间移动它,我必须使用盒装类型,例如Arc<dyn Clock + Send + Sync>
如果我使用具体类型 UtcClock
,则不需要。
是否提供了在 Rust 中使用 traits 惯用的解决方案?或者还有其他技术可以解耦 'interface' 和实现?
如果可以,那么有什么方法可以让它看起来比 Arc<dyn Clock + Send + Sync>
更好吗?
Arc<dyn Clock + Send + Sync>
似乎是最好的,除非您能够使用 &dyn Clock + Send + Sync
。然后,您可以使用别名类型使其看起来更好,例如type DynClock = Arc<dyn Clock + Send + Sync>;
。参见 the Rust book and the reference。
您不必使用特征对象(即某种 dyn Clock
),您也可以使用常规泛型:
fn use_clock<T: Clock>(clock: T) {
let time = clock.now();
// ...
}
虽然我想你会有一个包含各种“服务”的结构,并且到处都有很多泛型可能会有点混乱。
至于特征对象,如果你只是想“不必考虑它”并获得 Java-like 行为,Arc<dyn Clock>
将在很大程度上做你想做的,除了 thread-safety 相关特征。要解决这些问题,您可以编写 Arc<dyn Clock + Send + Sync + 'static>
,但我更喜欢制作这些超级特性:
trait Clock: Send + Sync + 'static /* + whatever other traits you want */ {
// ...
}
请注意,此语法 不会像 Java 中的等效语法那样进行继承。相反,它施加了以下限制:实现 Clock
的 类型也必须实现 Send
,等等 。特别是在使用单元结构(隐式 Send + Sync + 'static
)时,将约束移至特征定义有助于减少样板文件。
还有类型别名,它基本上允许您更改类型的“拼写”:
type ArcClock = Arc<dyn Clock + Send + Sync + 'static>;
然后你可以像普通类型一样使用 ArcClock
。请注意,对于编译器而言,ArcClock
与 Arc<dyn Clock + ...>
.
顺便说一句,在 Rust 中,trait 对象经常因为“性能差”而被看不起。虽然在 trait 对象上调用方法通常比常规的单态泛型更难优化并且需要额外的指针间接寻址,但除非您在紧密循环中调用该方法,否则这种开销可能相当小。
例如,对于存储用户创建时间的网络服务器,特征对象的开销可以忽略不计。 FWIW,所有 java 方法调用都以这种方式运行,因此在最坏的情况下您将获得 Java-tier 性能。