繁体   English   中英

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

[英]the trait cannot be made into an object because method `merge` references the `Self` type in this parameter - rust

我正在尝试通过撤消板条箱为我的应用程序实现撤消/重做系统。 我想要一个全局可访问的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();
}

这给出了错误:

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

有没有办法解决这个问题?

这是问题特征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
    }
}

您似乎正在尝试创建一个可以包含不同类型Action的历史记录,但这并不是undo的用途。 您实现Action的类型旨在表示历史中所有可能的操作,因此如果有多种类型,您通常会在enum上实现Action来表示这一点。 如果您想使用特征 object 的原因只是为了避免在Action impl 的所有方法中使用match语句,我建议您使用 crate enum_dispatch为您生成此样板。 但是,如果您有充分的理由使用特征而不是枚举(即,您需要应用程序中的一组操作可以被其他代码扩展),那么特征 object 方法可以工作,尽管它会更复杂,更慢一点,可读性更差。 我在下面说明了两种可能的解决方案:

使用enum_dispatch

enum_dispatch只是自动为由实现该特征的类型组成的枚举实现特征。 为了使用它来创建可以实现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)
    }
} 

使用此解决方案,您可以通过匹配可以与Self合并的类型的第二个参数并使用_ => Merged::No来忽略其他所有内容,从而为特定的具体类型实现AppAction::merge

使用特征 object 和as_any

如果你仍然想使用动态调度,就像之前你可以创建你自己的 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());
    }
}

但是,现在出现的问题是,当您不知道其他操作的具体类型时,如何实际实现merge方法。 为此,您将不得不使用Any来尝试向下转换为可以合并操作的每个具体类型。 理想情况下,这就像在merge中使用&mut dyn MyAction + Any和在History中使用Box<dyn MyAction + Any>一样简单,但是 Rust 目前不支持复合特征对象。 基于使Any成为您的 trait 的超特征,有一个有点复杂的解决方法,但您最好使用 crate as_any为您处理这个问题。 一旦你让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
}

同样,这个选项是两者中最令人困惑的,所以只有在你真的需要时才使用它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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