[英]the trait cannot be made into an object because method `merge` references the `Self` type in this parameter - rust
I'm trying to implement an undo/redo system for my app via the undo crate.我正在尝试通过撤消板条箱为我的应用程序实现撤消/重做系统。 I want a globally accessible
HISTORY
variable to which I can push Box<dyn Action>
trait objects.我想要一个全局可访问的
HISTORY
变量,我可以将Box<dyn Action>
特征对象推送到该变量。
use undo::{Action, History};
lazy_static::lazy_static! {
static ref HISTORY: History<Box<dyn Action<Output = (), Error = String, Target = AppState>>> = History::new();
}
This gives error:这给出了错误:
error[E0038]: the trait `Action` 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>
--> C:\Users\Primary User\.cargo\registry\src\github.com-1ecc6299db9ec823\undo-0.46.0\src\lib.rs:135:8
|
135 | fn merge(&mut self, _: &mut Self) -> Merged {
| ^^^^^ the trait cannot be made into an object because method `merge` references the `Self` type in this parameter
Is there any way to work around this?有没有办法解决这个问题?
Here's the problem trait Action
, from the undo library source code:这是问题特征
Action
,来自撤消库源代码:
pub trait Action {
/// The target type.
type Target;
/// The output type.
type Output;
/// The error type.
type Error;
/// Applies the action on the target and returns `Ok` if everything went fine,
/// and `Err` if something went wrong.
fn apply(&mut self, target: &mut Self::Target) -> Result<Self>;
/// Restores the state of the target as it was before the action was applied
/// and returns `Ok` if everything went fine, and `Err` if something went wrong.
fn undo(&mut self, target: &mut Self::Target) -> Result<Self>;
/// Reapplies the action on the target and return `Ok` if everything went fine,
/// and `Err` if something went wrong.
///
/// The default implementation uses the [`apply`](trait.Action.html#tymethod.apply) implementation.
fn redo(&mut self, target: &mut Self::Target) -> Result<Self> {
self.apply(target)
}
/// Used for manual merging of actions.
fn merge(&mut self, _: &mut Self) -> Merged {
Merged::No
}
}
You seem to be attempting to create a history that can contain different types of Action
, but this is not how undo
is meant to be used.您似乎正在尝试创建一个可以包含不同类型
Action
的历史记录,但这并不是undo
的用途。 The type for which you implement Action
is intended to represent all possible actions in the history, so if there are multiple kinds you would typically implement Action
on an enum
to represent this.您实现
Action
的类型旨在表示历史中所有可能的操作,因此如果有多种类型,您通常会在enum
上实现Action
来表示这一点。 If the reason you want to use a trait object is simply to avoid match
statements in the all methods of your Action
impl, I would recommend using the crate enum_dispatch
to generate this boilerplate for you.如果您想使用特征 object 的原因只是为了避免在
Action
impl 的所有方法中使用match
语句,我建议您使用 crate enum_dispatch
为您生成此样板。 However, if you have a very good reason to use a trait instead of an enum (ie you need the set of actions in your app to be extensible by other code), then the trait object approach can be made to work, though it will be more complicated, a little slower, and less readable.但是,如果您有充分的理由使用特征而不是枚举(即,您需要应用程序中的一组操作可以被其他代码扩展),那么特征 object 方法可以工作,尽管它会更复杂,更慢一点,可读性更差。 I have illustrated both possible solutions below:
我在下面说明了两种可能的解决方案:
enum_dispatch
enum_dispatch
enum_dispatch
simply automatically implements a trait for an enum composed of types that implement that trait. enum_dispatch
只是自动为由实现该特征的类型组成的枚举实现特征。 In order to use it to create a type you can implement Action
for, you would create your own trait AppAction
that is just like undo::Action
, except that the return types are fixed and the second argument of merge
takes the enum type rather than Self
, like so:为了使用它来创建可以实现
Action
的类型,您将创建自己的特征AppAction
,就像undo::Action
一样,除了返回类型是固定的并且merge
的第二个参数采用枚举类型而不是Self
,像这样:
#[enum_dispatch(AppActionEnum)]
pub trait AppAction {
fn apply(&mut self, target: &mut AppState) -> Result<(), String>;
fn undo(&mut self, target: &mut AppState) -> Result<(), String>;
fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
self.apply(target)
}
fn merge(&mut self, _: &mut AppActionEnum) -> Merged {
Merged::No
}
}
#[enum_dispatch]
pub enum AppActionEnum {
MyAction1,
MyAction2,
...
}
impl Action for AppActionEnum {
type Target = AppState;
type Output = ();
type Error = String;
fn apply(&mut self, target: &mut AppState) -> Result<(), String> {
AppAction::apply(self, target)
}
fn undo(&mut self, target: &mut AppState) -> Result<(), String> {
AppAction::undo(self, target)
}
fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
AppAction::redo(self, target)
}
fn merge(&mut self, other: &mut AppActionEnum) -> Merged {
AppAction::merge(self, other)
}
}
With this solution, you can implement AppAction::merge
for a particular concrete type by matching on the second argument for types that can merge with Self
and using _ => Merged::No
to ignore everything else.使用此解决方案,您可以通过匹配可以与
Self
合并的类型的第二个参数并使用_ => Merged::No
来忽略其他所有内容,从而为特定的具体类型实现AppAction::merge
。
as_any
as_any
If you would still like to use dynamic dispatch, just like before you can create your own trait AppAction
, but this time make the argument of merge
a trait object, and implement undo::Action
for boxes of that type.如果你仍然想使用动态调度,就像之前你可以创建你自己的 trait
AppAction
,但是这次将参数merge
为一个 trait object,并为该类型的盒子实现undo::Action
。
pub trait AppAction {
fn apply(&mut self, target: &mut AppState) -> Result<(), String>;
fn undo(&mut self, target: &mut AppState) -> Result<(), String>;
fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
self.apply(target)
}
fn merge(&mut self, _: &mut dyn AppAction) -> Merged {
Merged::No
}
}
impl Action for Box<dyn AppAction> {
type Target = AppState;
type Output = ();
type Error = String;
fn apply(&mut self, target: &mut AppState) -> Result<(), String> {
self.deref_mut().apply(target);
}
fn undo(&mut self, target: &mut AppState) -> Result<(), String> {
self.deref_mut().undo(target);
}
fn redo(&mut self, target: &mut AppState) -> Result<(), String> {
self.deref_mut().redo(target);
}
fn merge(&mut self, other: &mut Box<dyn MyAction>) -> Merged {
self.deref_mut().merge(other.deref_mut());
}
}
However, now comes the question of how to actually implement the merge
method when you don't know the concrete type of the other action.但是,现在出现的问题是,当您不知道其他操作的具体类型时,如何实际实现
merge
方法。 For this, you will have to use Any
to attempt to downcast to each concrete type the action could be merged with.为此,您将不得不使用
Any
来尝试向下转换为可以合并操作的每个具体类型。 Ideally, this would be as simple as using &mut dyn MyAction + Any
in merge
and Box<dyn MyAction + Any>
in your History
, however Rust does not support compound trait objects at present.理想情况下,这就像在
merge
中使用&mut dyn MyAction + Any
和在History
中使用Box<dyn MyAction + Any>
一样简单,但是 Rust 目前不支持复合特征对象。 There is a somewhat involved workaround based on making Any
a supertrait of your trait, but you would be better off using the crate as_any
to handle this for you.基于使
Any
成为您的 trait 的超特征,有一个有点复杂的解决方法,但您最好使用 crate as_any
为您处理这个问题。 Once you make as_any::AsAny
a supertrait of AppAction
and impl as_any::Downcast
for dyn AppAction
, you can write a merge implementation like so:一旦你让
as_any::AsAny
成为 AppAction 的AppAction
并为dyn AppAction
实现as_any::Downcast
Downcast ,你就可以像这样编写一个合并实现:
fn merge(&mut self, other: &mut dyn MyAction) -> Merged {
if let Some(other) = other.downcast_mut::<MyOtherAction1>() {
// handle actions of type MyOtherAction1
return Merged::Yes // or Merged::Annul if appropriate
}
if let Some(other) = other.downcast_mut::<MyOtherAction2>() {
// handle actions of type MyOtherAction2
return Merged::Yes // or Merged::Annul if appropriate
}
// and so on for all the other types this action can merge with...
Merged::No
}
Again, this option is by far the more confusing of the two, so only use it if you really need to.同样,这个选项是两者中最令人困惑的,所以只有在你真的需要时才使用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.