我正在研究的代码实现了几个结构体的多个特征。有一个工厂方法生成这些结构之一的对象并将其返回给我的代码。对象作为这些特征之一返回,但是我想使用一个包含泛型的特征的方法。
下面的代码说明了我的问题:
struct Data {
some_data: String
}
struct Data2 {
some_data: i32
}
trait PrintableData {
fn print(&mut self);
}
trait ConsumableData {
fn consume<Callback>(&self, callback: Callback)
where
Callback: FnMut(String);
}
impl Data {
fn new(data: String) -> Self {
Data {
some_data: data
}
}
}
impl Data2 {
fn new(data: i32) -> Self {
Data2 {
some_data: data
}
}
}
impl PrintableData for Data {
fn print(&mut self) {
println!("{}", self.some_data);
}
}
impl PrintableData for Data2 {
fn print(&mut self) {
println!("{}", self.some_data);
}
}
impl ConsumableData for Data {
fn consume<Callback>(&self, mut callback: Callback) where Callback: FnMut(String) {
callback(self.some_data.clone());
}
}
impl ConsumableData for Data2 {
fn consume<Callback>(&self, mut callback: Callback) where Callback: FnMut(String) {
callback(self.some_data.to_string());
}
}
fn factory(data_type: i32) -> Box<dyn PrintableData> {
match data_type {
1 => Box::new(Data::new(String::from("123"))),
2 => Box::new(Data2::new(123)),
_ => panic!("panic")
}
}
fn main() {
let callback = |s: String| {};
for data_type in 1..3 {
let d = factory(data_type);
(d as Box<dyn ConsumableData>).consume(callback);
}
}
简而言之,工厂函数返回Data
或Data2
作为PrintableData
,但我需要使用对象作为ConsumableData
,其中实现了一个泛型方法。
运行此代码会产生以下错误:
error[E0038]: the trait `ConsumableData` cannot be made into an object
--> src/main.rs:72:15
|
72 | (d as Box<dyn ConsumableData>).consume(callback);
| ^^^^^^^^^^^^^^^^^^^^^^^ `ConsumableData` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:14:8
|
13 | trait ConsumableData {
| -------------- this trait cannot be made into an object...
14 | fn consume<Callback>(&self, callback: Callback)
| ^^^^^^^ ...because method `consume` has generic type parameters
= help: consider moving `consume` to another trait
首先,您需要使ConsumableData
对象安全,因此您将能够使用它。
我们可以从Iterator
这里吸取教训:它有一些非对象安全的方法可以使用self
,例如map()
。尽管如此,它仍然是对象安全的,并且您可以非常方便地使用dyn Iterator
。秘诀是分三个阶段:
- 有一个对象安全的核心方法(
Iterator::next()
)。 - 根据它实现所有其他方法(可能也允许专门用于优化)。
- 有一个毯子实现转发引用(和
Box
)的实现。
现在,对于普通对象,您可以直接调用非对象安全的方法。但是当使用dyn Iterator
时,您不会调用<dyn Iterator>::map()
,因为它不是对象安全的,而是调用<&mut dyn Iterator>::map()
。这是非常好的,因为&mut dyn Iterator
是Sized
。该方法将实际工作转发给<&mut dyn Iterator>::next()
(通过其默认实现),然后再将其转发给实际的<dyn Iterator>::next()
——它可以这样做,因为next()
是对象安全的!
我们可以通过将consume()
对象从泛型更改为&mut dyn FnMut
来使其安全:
trait ConsumableData {
fn consume_dyn(&self, callback: &mut dyn FnMut(String));
fn consume<Callback>(&self, mut callback: Callback)
where
Callback: FnMut(String),
Self: Sized,
{
self.consume_dyn(&mut callback)
}
}
和毛毯实现:
impl<T: ?Sized + ConsumableData> ConsumableData for &'_ T {
fn consume_dyn(&self, callback: &mut dyn FnMut(String)) {
T::consume_dyn(&**self, callback)
}
}
impl<T: ?Sized + ConsumableData> ConsumableData for &'_ mut T {
fn consume_dyn(&self, callback: &mut dyn FnMut(String)) {
T::consume_dyn(&**self, callback)
}
}
impl<T: ?Sized + ConsumableData> ConsumableData for Box<T> {
fn consume_dyn(&self, callback: &mut dyn FnMut(String)) {
T::consume_dyn(&**self, callback)
}
}
现在实现者应该实现(仅或不实现,取决于所需的性能特征)consume_dyn()
,但他们免费获得consume()
。
最后一个组件是如何从Box<dyn PrintableData>
转换到Box<dyn ConsumableData>
。这可以通过在PrintableData
特性中提供一个方法来完成:
trait PrintableData {
// ...
fn into_consumable_data(self: Box<Self>) -> Box<dyn ConsumableData>;
}
impl PrintableData for Data {
// ...
fn into_consumable_data(self: Box<Self>) -> Box<dyn ConsumableData> {
self
}
}
impl PrintableData for Data2 {
// ...
fn into_consumable_data(self: Box<Self>) -> Box<dyn ConsumableData> {
self
}
}
最后:
let d = factory(data_type);
d.into_consumable_data().consume(callback);
操场。