我正在编写一个 Rust 板条箱,其中有一个结构体,其字段实现了一个特征,并且我想提供一个功能:
#[cfg(not(feature = "dyn"))]
:结构字段是T
静态分派的(性能更高,但运行时灵活性较差)#[cfg(feature = "dyn")]
:结构字段是Box<dyn MyTrait>
,动态分派的特征对象(运行时灵活性,性能成本较低)
trait MyTrait {}
#[cfg(not(feature = "dyn"))]
struct MyStruct<T: MyTrait> {
my_field: T
}
#[cfg(feature = "dyn")]
struct MyStruct {
my_field: Box<dyn MyTrait>
}
我的问题是,每一个impl
块都需要完全复制,但更改只会影响几行。
#[cfg(not(feature = "dyn"))]
impl<T: MyTrait> MyStruct<T> {
fn new(field: T) -> Self {
Self { field }
}
}
#[cfg(feature = "dyn")]
impl MyStruct {
fn new(field: impl MyTrait + 'static) -> Self {
Self { field: Box::new(field) }
}
}
有没有更优雅的方式来做到这一点?宏黑魔法?这只是一个坏主意,不值得做吗?
编辑
根据@cdhowie 的回复
这几乎可以成功编译!但是MyTrait
实际上有一个方法(我将其删除以获得一个最小示例):
trait MyTrait: Sized {
fn calculate(&self, struct: &MyStruct<Self>);
}
&MyStruct<Self>
由于此方法需要访问数据进行计算,因此引用是必要的。
尝试按照建议执行,但不起作用……这会破坏动态兼容性
impl MyTrait for Box<dyn MyTrait> {
// ^^^^^^^^^^^^^^^^
// ERROR
// the trait `MyTrait` cannot be made into an object
如果我尝试实现 Box 的特征(不确定这是否能解决我的问题),那么麻烦就来了:
impl<T: MyTrait> MyTrait for Box<T> {
fn calculate(
&self,
struct: &MyStruct<Self>,
) {
(**self).calculate(struct)
// ^^^^^^
// ERROR: mismatched types
// expected reference &MyStruct<T>
// found reference &MyStruct<Box<T>>
}
}
实际上,使用条件编译来执行此操作的理由很少。相反,只需实现
MyTrait
onBox<dyn MyTrait>
,然后消费者就可以使用MyStruct<Box<dyn MyTrait>>
动态调度。您也可以默认
T
为Box<dyn MyTrait>
,然后单纯使用MyStruct
将隐式使用动态调度:首先,不要。功能应该是附加的,因为功能是统一的。想象一下,有两个库依赖于您的库,一个库依赖
feature = "dyn"
另一个库。它们单独编译时会很好,但任何试图依赖两者的人都会导致它们无法编译(并且会带来非常严重的麻烦)。现在,假设您确实没有可行的 @cdhowie 答案变体(我还没有彻底检查过这一事实),最好的解决方案是拥有两个结构和两个特征。当然,它们可以尽可能共享代码,但 API 应该有所不同。