Assign a struct implementation to another struct implementation

I’d like to keep an instance of the handlebars registry in a struct named "Mailer".

The instance is to be initialized upon running the "new" function of the Mailer implementation.

I do have massive problems in figuring out the proper ownership at each scope.

use handlebars::Handlebars; 

pub struct Mailer<'reg> {
   registry: &'reg handlebars::Handlebars<'reg>
}

impl <'reg>Mailer<'reg> {
    pub fn new() -> &'reg Mailer<'reg> {
       let registry = handlebars::Handlebars::new();       
       Mailer { registry: &registry }
    }
}

fn main() -> Result<(), std::io::Error> {
    let mailer = Mailer::new();
    Ok(())
}

The compiler presents these errors:

error[E0308]: mismatched types
  --> src/utils/mailer.rs:9:8
   |
8  |     pub fn new() -> &'reg Mailer<'reg> {
   |                     ------------------ expected `&'reg Mailer<'reg>` because of return type
...
9  |        Mailer { registry: &registry }
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |        |
   |        expected `&Mailer<'reg>`, found struct `Mailer`
   |        help: consider borrowing here: `&Mailer { registry: &registry }`

Basically I don’t know how to annotate the returned Mailer Struct properly.

Simply returning

&Mailer<'req> { registry: &registry }

instead of

Mailer { registry: &registry }

is not a remedy at all.

I am definitely missing a lot about ownership in the Rust documentation. In my defense, I’ve not found any example or explanation covering struct implementations assigning other struct implementations.

I am seriously lost here.

>Solution :

Your new function returns a &Mailer<'_> (with lifetimes hidden), but your final expression is Mailer { ... }, which has type Mailer<'_>, not &Mailer<'_>, so you get an error.

Simply making it return &Mailer { ... } doesn’t work for a different reason.

Let’s imagine writing it like this, which is equivalent:

fn new() -> &'reg Mailer<'reg> {
  let registry = handlebars::Handlebars::new();       
  let mailer = Mailer { registry: &registry };
  return &mailer;
}

In Rust, the only way to pass ownership of some data from a function to its caller is by returning it. For example, if I create a Vec in a function, and I want to pass ownership to the caller, I must return it:

fn make_vec() -> Vec<i32> {
  vec![1, 2, 3]
}

The calling code takes ownership of the vec, and is therefore responsible for dropping it.

Going back to your code, you’re creating a Mailer, but you’re not returning it to the caller, you’re only returning a reference to it. Ownership stays inside new, and so new is responsible for dropping it.

When the function returns, mailer is then dropped, since it’s reached the end of the scope it was declared in, but we’ve kept a reference to it (by returning it to the caller). This isn’t allowed, since it would allow use-after-free (using a reference to some data after that data has been dropped).

I haven’t worked with handlebars in Rust before, but you probably want to change your code to look like this:

struct Mailer<'reg> {
  registry: handlebars::Handlebars<'reg>  // no reference here
}

impl<'reg> Mailer<'reg> {
  fn new() -> Self {
    let registry = handlebars::Handlebars::new();
    Self { registry }  // shorthand for Self { registry: registry }
  }
}

The key differences are:

  • Mailer takes an owned Handlebars instance, rather than a reference to one
  • new returns an owned Mailer

This way, full ownership is passed to the caller of Mailer::new

Leave a Reply