我要在Rust中重写一个高度模块化的CMS,所以我的问题是,如果甚至可以让“核心”应用程序设置扩展点(动作/钩子),其他插件/板条箱能够“标签“进入。
Something like this就足够了,但你怎么能在Rust做到这一点?上面的体系结构使用插件注册表,并通过遍历每个插件的主要方法来迭代每个插件的主要方法。但是在Rust中,因为你不能拥有例如全局“模块”变量。一个plugin_registry lib crate,我想这不是Rust的正确思路。
是否有更好,更灵活的方式使“插件”与核心应用程序无缝集成?例如,像WordPress这样的事件调度程序之类的东西使用?
答案 0 :(得分:7)
正如Shepmaster所说,这是一个非常普遍的问题;因此,有很多方法可以做你想要的。如前所述,iron
也是模块化框架的一个很好的例子。
但是,我将尝试提供一个如何实现此类插件系统的有用示例。对于我将假设的示例,有某种主板可以加载插件并且"配置" CMS。这意味着插件没有动态加载!
首先,假设我们有四个板条箱:
rustpress
:具有所有类似WordPress功能的主要包装箱rustpress-plugin
:需要被插件作者使用(是一个自己的包,以避免为每个插件使用像rustpress
这样的巨大垃圾箱)rustpress-signature
:这里我们创建了一个插件,为每个帖子添加签名my-blog
:这将是配置我们博客的主要可执行文件,并将在以后作为Web服务器运行 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_*
方法已经有一个不执行任何操作的默认实现({}
)。这意味着如果插件只想使用一个挂钩,则插件不必覆盖所有方法。
正如我所说,我们想要编写一个插件,将我们的签名添加到每个帖子主体。要做到这一点,我们将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()
因为我们不需要。
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然后准备博客帖子时,它会遍历所有插件并调用所有挂钩。
最后一步是添加插件并启动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。