Reputation: 2332
I am writing a tool that generates Rust code, and I've come up with the following pattern that is being generated:
pub fn my_template() {
let mut o = HtmlGenerator::new();
o.paragraph(&mut || {
o.emphasis(&mut || {
o.normal_text("hello");
});
});
}
The paragraph
and emphasis
methods have the type:
fn paragraph(&mut self, cnt: &mut impl FnMut());
I get the following error:
|
197 | o.paragraph(&mut || {
| ^ --------- -- first mutable borrow occurs here
| | |
| _________| first borrow later used by call
| |
198 | | o.normal_text("hello");
| | - first borrow occurs due to use of `o` in closure
199 | | });
| |__________^ second mutable borrow occurs here
Apparently I cannot borrow the mutable reference twice. It needs to be mutable because some of the generator functions might change the state of the HtmlGenerator
, which is why I use methods in the first place.
Can this pattern be implemented in Rust? Am I trying to create nonsense?
Upvotes: 0
Views: 84
Reputation: 154911
The pattern cannot be implemented with exactly the provided signatures of paragraph()
and other methods. But it's possible to get the same effect with just minor changes to either the closure signature or the method signatures.
The simplest and cleanest solution would be to pass the HtmlGenerator
to the closures:
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
}
}
An alternative is to leave the closure signature as-is, but use interior mutability to make methods like paragraph()
accept &self
, at a very tiny run-time cost. For example:
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()
}
}
It may appear wrong to modify the data from an "immutable" method, but &self
just means that the data is safe to share. self.data.borrow_mut()
will panic if the borrow is already active, but that can't happen in the above code because the borrow is released as soon as the underlying data is modified. If we were to hold on to the guard returned by borrow_mut()
and invoke the closure with the guard object live, the inner borrow_mut()
in normal_text()
invoked by the closure would panic.
In both approaches the trait bound for the closures can be the most permissive FnOnce
, as the methods only invoke them once.
Upvotes: 7
Reputation: 42272
My question now is whether this pattern can even be ipmlemented in rust or whether I'm trying to create nonsense here?
It can't work like this, because the closure closes over the generator in order to implement the inner call, so the closure and the caller's borrow will necessarily overlap, which can not work.
What can work is for the generator methods to pass in the generator instance to the closures they call:
pub fn my_template() {
let mut o = HtmlGenerator::new();
o.paragraph(|o: &mut HtmlGenerator| {
o.emphasis(|o: &mut HtmlGenerator| {
o.normal_text("hello");
});
});
or something like that.
I also think you're probably better off passing the callable by value:
fn paragraph(&mut self, mut cnt: impl FnMut());
Upvotes: 2