简体   繁体   English

该特征不能成为 object,因为方法 `merge` 引用了此参数中的 `Self` 类型 - rust

[英]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:我在下面说明了两种可能的解决方案:

Using 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

Using a trait object and as_any使用特征 object 和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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM