是否可以在Rust中制作插件扩展挂钩,如WordPress操作?

时间:2016-01-23 20:29:07

标签: rust

我要在Rust中重写一个高度模块化的CMS,所以我的问题是,如果甚至可以让“核心”应用程序设置扩展点(动作/钩子),其他插件/板条箱能够“标签“进入。

Something like this就足够了,但你怎么能在Rust做到这一点?上面的体系结构使用插件注册表,并通过遍历每个插件的主要方法来迭代每个插件的主要方法。但是在Rust中,因为你不能拥有例如全局“模块”变量。一个plugin_registry lib crate,我想这不是Rust的正确思路。

是否有更好,更灵活的方式使“插件”与核心应用程序无缝集成?例如,像WordPress这样的事件调度程序之类的东西使用?

1 个答案:

答案 0 :(得分:7)

正如Shepmaster所说,这是一个非常普遍的问题;因此,有很多方法可以做你想要的。如前所述,iron也是模块化框架的一个很好的例子。

但是,我将尝试提供一个如何实现此类插件系统的有用示例。对于我将假设的示例,有某种主板可以加载插件并且"配置" CMS。这意味着插件没有动态加载!

结构

首先,假设我们有四个板条箱:

  • rustpress:具有所有类似WordPress功能的主要包装箱
  • rustpress-plugin:需要被插件作者使用(是一个自己的包,以避免为每个插件使用像rustpress这样的巨大垃圾箱)
  • rustpress-signature:这里我们创建了一个插件,为每个帖子添加签名
  • my-blog:这将是配置我们博客的主要可执行文件,并将在以后作为Web服务器运行

1。特征/界面

Rust的方法是trait。您可以将它们与其他语言的接口进行比较。我们现在将为trait中的插件设计rustpress-plugin

pub trait Plugin {
    /// Returns the name of the plugin
    fn name(&self) -> &str;
    /// Hook to change the title of a post
    fn filter_title(&self, title: &mut String) {}
    /// Hook to change the body of a post
    fn filter_body(&self, body: &mut String) {}
}

请注意,filter_*方法已经有一个不执行任何操作的默认实现({})。这意味着如果插件只想使用一个挂钩,则插件不必覆盖所有方法。

2。写我们的插件

正如我所说,我们想要编写一个插件,将我们的签名添加到每个帖子主体。要做到这一点,我们将impl我们自己类型的特征(在rustpress-signature中):

extern crate rustpress_plugin;
use rustpress_plugin::Plugin;

pub struct Signature {
    pub text: String,
}

impl Plugin for Signature {
    fn name(&self) -> &str {
        "Signature Plugin v0.1 by ferris"
    }

    fn filter_body(&self, body: &mut String) {
        body.push_str("\n-------\n");   // add visual seperator 
        body.push_str(&self.text);
    }
}

我们创建了一个简单类型Signature,我们为其实现了特征Plugin。我们必须实现name()方法,我们还会覆盖filter_body()方法。在我们的实现中,我们只是向帖子正文添加文本。我们没有覆盖filter_title()因为我们不需要。

3。实现插件堆栈

CMS必须管理所有插件。我假设CMS有一个主要类型RustPress,可以处理所有事情。它看起来像这样(在rustpress中):

extern crate rustpress_plugin;
use rustpress_plugin::Plugin;

pub struct RustPress {
    // ...
    plugins: Vec<Box<Plugin>>,
}

impl RustPress {
    pub fn new() -> RustPress {
        RustPress {
            // ...
            plugins: Vec::new(),
        }
    }

    /// Adds a plugin to the stack
    pub fn add_plugin<P: Plugin + 'static>(&mut self, plugin: P) {
        self.plugins.push(Box::new(plugin));
    }

    /// Internal function that prepares a post
    fn serve_post(&self) {
        let mut title = "dummy".to_string();
        let mut body = "dummy body".to_string();

        for p in &self.plugins {
            p.filter_title(&mut title);
            p.filter_body(&mut body);
        }

        // use the finalized title and body now ...
    }

    /// Starts the CMS ...
    pub fn start(&self) {}
}

我们在这里做的是存储一个Vec个装满盒装的插件(我们需要将它们装箱,因为我们需要所有权,但是特征是未分级的)。当CMS然后准备博客帖子时,它会遍历所有插件并调用所有挂钩。

4。配置并启动CMS

最后一步是添加插件并启动CMS(将它们放在一起)。我们将在my-blog包中执行此操作:

extern crate rustpress;
extern crate rustpress_plugin;
extern crate rustpress_signature;

use rustpress::RustPress;
use rustpress_plugin::Plugin;
use rustpress_signature::Signature;

fn main() {
    let mut rustpress = RustPress::new();

    // add plugin
    let sig = Signature { text: "Ferris loves you <3".into() };
    rustpress.add_plugin(sig);

    rustpress.start();
}

您还需要将依赖项添加到Cargo.toml文件中。我省略了它,因为它应该相当容易。

再次注意,这是创建这样一个系统的许多可能性的一个。我希望这个例子很有帮助。您也可以尝试on playground