我正在编写一个生成 Rust 代码的工具,我想出了以下正在生成的模式:
pub fn my_template() {
let mut o = HtmlGenerator::new();
o.paragraph(&mut || {
o.emphasis(&mut || {
o.normal_text("hello");
});
});
}
Run Code Online (Sandbox Code Playgroud)
该paragraph和emphasis方法具有类型:
fn paragraph(&mut self, cnt: &mut impl FnMut());
Run Code Online (Sandbox Code Playgroud)
我收到以下错误:
pub fn my_template() {
let mut o = HtmlGenerator::new();
o.paragraph(&mut || {
o.emphasis(&mut || {
o.normal_text("hello");
});
});
}
Run Code Online (Sandbox Code Playgroud)
显然我不能两次借用可变引用。它需要是可变的,因为一些生成器函数可能会改变HtmlGenerator,这就是我首先使用方法的原因。
这个模式可以在 Rust 中实现吗?我是在胡说八道吗?
该模式不能完全使用提供的paragraph()和 其他方法的签名来实现。但是,只需对闭包签名或方法签名进行微小更改,就可以获得相同的效果。
最简单和最干净的解决方案是将 传递HtmlGenerator给闭包:
struct HtmlGenerator {
data: String,
}
impl HtmlGenerator {
fn new() -> HtmlGenerator {
HtmlGenerator {
data: "".to_string(),
}
}
fn paragraph(&mut self, inside: impl FnOnce(&mut HtmlGenerator)) {
self.data.push_str("<p>");
inside(self);
self.data.push_str("</p>");
}
fn normal_text(&mut self, text: &str) {
self.data.push_str(text);
}
fn into_data(self) -> String {
self.data
}
}
Run Code Online (Sandbox Code Playgroud)
另一种方法是保持闭包签名不变,但使用内部可变性以非常小的运行时成本来创建像paragraph()accept这样的方法&self。例如:
struct HtmlGenerator {
data: RefCell<String>,
}
impl HtmlGenerator {
fn new() -> HtmlGenerator {
HtmlGenerator {
data: RefCell::new("".to_string()),
}
}
fn paragraph(&self, inside: impl FnOnce()) {
self.data.borrow_mut().push_str("<p>");
inside();
self.data.borrow_mut().push_str("</p>");
}
fn normal_text(&self, text: &str) {
self.data.borrow_mut().push_str(text);
}
fn into_data(self) -> String {
self.data.into_inner()
}
}
Run Code Online (Sandbox Code Playgroud)
从“不可变”方法修改数据似乎是错误的,但这&self仅意味着数据可以安全共享。self.data.borrow_mut()如果借用已经处于活动状态,则会发生恐慌,但在上面的代码中不会发生这种情况,因为一旦底层数据被修改,借用就会被释放。如果我们保持由返回的守卫borrow_mut()并在守卫对象borrow_mut()处于活动状态的情况下normal_text()调用闭包,则闭包调用的内部in将发生恐慌。
在这两种方法中,闭包的 trait bound 可能是最宽松的FnOnce,因为这些方法只调用它们一次。