Store traits with same lifetime reference bound

#1

The title is not that great so I’ll explain more in details here.

I am working on the v1 of Tera, a template engine.

One of the feature is Function: in short a Rust function we can call from a template. It can be something simple like getting the current date to something operating on some context like: https://www.getzola.org/documentation/templates/overview/#get-page

In v1, Function is becoming the following trait:

pub trait Function: Sync + Send {
    fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;
}

The main perf bottleneck of Zola are those get_page/get_section that need access to the full context and which needed to be serialized. Ok when you have 200 pages, less when you have 100k.

I was hoping to use the fact that these are now traits to use references instead of cloning. I made an example of how it would ideally look like: https://github.com/Keats/tera/blob/lifetime-issue/examples/borrowed_trait_context/main.rs
This gets a lifetime error on https://github.com/Keats/tera/blob/lifetime-issue/examples/borrowed_trait_context/main.rs#L53 which is solved by making the reference 'site which in turns errors with:

error[E0597]: `site` does not live long enough
  --> examples/borrowed_trait_context/main.rs:66:5
   |
66 |     site.register_tera_fns();
   |     ^^^^ borrowed value does not live long enough
...
69 | }
   | -
   | |
   | `site` dropped here while still borrowed
   | borrow might be used here, when `site` is dropped and runs the destructor for type `Site<'_>`

error[E0499]: cannot borrow `site` as mutable more than once at a time
  --> examples/borrowed_trait_context/main.rs:67:13
   |
66 |     site.register_tera_fns();
   |     ---- first mutable borrow occurs here
67 |     rebuild(&mut site);
   |             ^^^^^^^^^
   |             |
   |             second mutable borrow occurs here
   |             first borrow later used here

All of those make sense but now I’m stuck. I do not want to borrow self.pages mutably but I have to to register the function on the Tera instance so I do need the mut.

It might be a case of XY problem and me trying to pigeonhole my original idea onto it.

Does anyone have solutions/alternative ideas to make it work while keeping references? Writing this made me think of maybe looking into crates for self-references from the user side?

In the case of Zola in particular, references will always be valid as Tera functions are re-registered on change and I am guessing for most other usecases other than Zola, cloning will be just fine perf wise since it will happen once.

0 Likes

#2

How about storing pages outside of Site? That is:

#[derive(Debug)]
struct Site<'site> {
    pages: &'site HashMap<String, String>,
    tera: Tera<'site>,
}

rebuild() should also be changed to:

fn rebuild<'a, 'site>(site: &'a mut Site<'site>) {
    site.register_tera_fns();
}

That’s the explicit version, but you probably want to use elision:

fn rebuild(site: &mut Site<'_>) {
    site.register_tera_fns();
}

Then main() looks like:

fn main() {
    let mut pages = HashMap::new();
    pages.insert("page1".to_string(), "Some content".to_string());
    pages.insert("page2".to_string(), "Some other content".to_string());

    let mut site = Site::new(&pages);

    site.register_tera_fns();
    rebuild(&mut site);
    println!("Done!");
}

Maybe the above doesn’t make actual logical sense though? In general though, I think you’ll want to have Site borrow pages, and not own them, if you also want to own the Tera instance in there.

0 Likes

#3

That’s the issue, the actual struct is https://github.com/getzola/zola/blob/master/components/site/src/lib.rs#L57-L76 and I’m not sure how I could split them since they are used in conjunction all the time.

Edit: to be clear, the struct that holds tons of data in this file is the Library and is what pages in the example code would be.

0 Likes

#4

I understand you have a self-referential struct situation, but can’t say I understand enough about Zola or Tera to suggest a concrete different design.

Have you considered using Rc to store the pages? That would give you cheap clones to pass around, and obviate some of the lifetimes.

0 Likes

#5

Thanks a lot, I was too focused on lifetimes but using Arc solved my actual issue!

0 Likes