Return sub-type, or use nested closures?

⚓ Rust    📅 2026-02-15    👤 surdeus    👁️ 1      

surdeus

I'm building a framework for generating docs, in which there's a trait Generator that takes in displayable items and generates the docs in its format.

There is a need for nesting items (e.g. links with text, lists, tables), and I'm trying to decide between two patterns. My first option is to return a Nest type that exclusively borrows the generator, which you can then use just like the original generator. Some pseudocode:

let mut g = ExampleMarkdownGenerator::new();

g.add("h1", "Hello, World!");

// mutably borrows `g`, so you can't add to it until
// `list` goes out of scope with `end`
let mut list = g.nest("ul");
list.add("p", "Item 1");
  
let mut bold = list.nest("b");
bold.add("p", "This is bold");
bold.end(); // we can use `list` again here
  
let mut list2 = list.nest("ul");
// and so on...
list2.end();

list.end();
// now, and only now, can we use the original `g` again.

Option two is to use closures to provide nesting. That might look like this:

let mut g = ExampleMarkdownGenerator::new();

g.add("h1", "Hello, World!");

g.nest("ul", |g| {
  g.add("p", "Item 1");
  
  g.nest("b", |g| {
    g.add("p", "This is bold");
  }); // bold ends here
  
  g.nest("ul", |g| {
    // and so on...
  });
}); // list ends here

The closure form is theoretically cleaner, by far (it avoids nasty issues around e.g. forgetting to explicitly end a nest); however, a major goal of this library is ergonomics, and I worry a little that the excessive closure nesting could become annoying with scopes and indentation.

So, what do you all think?

(Also, I'm sure the first technique has a name, but I can't think of it. If anyone knows, I'd appreciate a pointer to some existing literature on it. :slightly_smiling_face:)

1 post - 1 participant

Read full topic

🏷️ Rust_feed