Understanding Ownership?

Hello! New rustacean here, hopefully with a very basic question. I have a situation where I am trying to build a struct that represents a data bundle to be used later in the program. Populating this data bundle entails downloading a template file from S3 and adding it to a Minijinja environment. Here is the code:

use crate::services::s3::download_template;
use crate::util::terms::PRIMARY_TEMPLATE_NAME;
use minijinja::Environment;

pub struct ProgramContext<'a> {
  htmlTemplate: String,
  template: Environment<'a>,
}

impl<'a> ProgramContext<'a> {
  pub async fn new() -> Option<ProgramContext<'a>> {
    let mut me = Self {
      htmlTemplate: download_template().await?,
      template: Environment::new(),
    };

    me.template
      .add_template(&PRIMARY_TEMPLATE_NAME, &me.htmlTemplate);

    Some(me)
  }
}

I am getting the following error:

error[E0515]: cannot return value referencing local data `me.htmlTemplate`
  --> src/entities/program_context.rs:20:5
   |
18 |       .add_template(&PRIMARY_TEMPLATE_NAME, &me.htmlTemplate);
   |                                             ---------------- `me.htmlTemplate` is borrowed here
19 |
20 |     Some(me)
   |     ^^^^^^^^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `me` because it is borrowed
  --> src/entities/program_context.rs:20:10
   |
10 | impl<'a> ProgramContext<'a> {
   |      -- lifetime `'a` defined here
...
18 |       .add_template(&PRIMARY_TEMPLATE_NAME, &me.htmlTemplate);
   |                                             ---------------- borrow of `me.htmlTemplate` occurs here
19 |
20 |     Some(me)
   |     -----^^-
   |     |    |
   |     |    move out of `me` occurs here
   |     returning this value requires that `me.htmlTemplate` is borrowed for `'a`

Some errors have detailed explanations: E0277, E0505, E0515.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `render-engine-rust` due to 3 previous errors

I am worried that I am fundamentally misunderstanding the concepts here, as this seems like it should be fine to me. I am giving ownership of the htmlTemplate to the struct variable that I am ultimately returning. Would anyone be able to help me understand what is going wrong here? Thank you in advance

Borrowed data has to not be moved until it is no longer borrowed. htmlTemplate is borrowed but returning Some(me) moves it.

Also, it's never correct to use a lifetime parameter on a struct 'a to refer to data that is stored within the struct itself.

1 Like

Skimming the docs, you probably want to hold a Source and add your template to that, and then set that as the source for the Environment. I didn't try it myself, but something like...

pub struct ProgramContext {
  template: Environment<'static>,
}

impl ProgramContext {
  pub async fn new() -> Option<ProgramContext> {
    let template = download_template().await?;
    let mut source = Source::new();
    source.add_template(&PRIMARY_TEMPLATE_NAME, template);
    let template = Environment::new();
    template.set_source(source);

    Self { template }
  }
}
1 Like

That really did the trick! I also noticed in the docs that the author mentions that due to the reference in the Environment:: add_template function source parameter, it makes it a PTA to pass around. Using the Source struct totally solved that issue. Thank you!

Thank you for the tips! The solution posted just sets the static lifetime to the struct which resolves your comment there. I appreciate the feedback!

Note that self-referential types are essentially impossible in safe Rust. If a direct self-referential type was possible, it would obviously lead to unsoundness, since moving it around would invalidate the self-reference (the address stored in the reference wouldn't magically change to point to the new location).

In general, you shouldn't be using self-referential types, they are a sign of a lack of understanding of the ownership system more often than not. Separate your types into owning and borrowing parts instead, if necessary.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.