用泛型将对象从一个trait转换为另一个trait



我正在研究的代码实现了几个结构体的多个特征。有一个工厂方法生成这些结构之一的对象并将其返回给我的代码。对象作为这些特征之一返回,但是我想使用一个包含泛型的特征的方法。

下面的代码说明了我的问题:

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);
}
}

简而言之,工厂函数返回DataData2作为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。秘诀是分三个阶段:

  1. 有一个对象安全的核心方法(Iterator::next())。
  2. 根据它实现所有其他方法(可能也允许专门用于优化)。
  3. 有一个毯子实现转发引用(和Box)的实现。

现在,对于普通对象,您可以直接调用非对象安全的方法。但是当使用dyn Iterator时,您不会调用<dyn Iterator>::map(),因为它不是对象安全的,而是调用<&mut dyn Iterator>::map()。这是非常好的,因为&mut dyn IteratorSized。该方法将实际工作转发给<&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);

操场。

最新更新