这种模式可以在 Rust 中实现吗?

Joh*_*ith 0 rust

我正在编写一个生成 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)

paragraphemphasis方法具有类型:

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 中实现吗?我是在胡说八道吗?

use*_*342 7

该模式不能完全使用提供的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,因为这些方法只调用它们一次。