Newbie: cannot infer an appropriate lifetime for autoref due to conflicting requirements

Our team is just getting started with rust and we are stuck on this. There seem to be quite a few posts around the topic, but we are struggling to apply the answers to our situation. We have the following code:

use lambda_runtime::handler_fn;
use worker::lambda_handler::LambdaHandler;
use worker::mailer::Mailer;
use worker::mailgun::MailgunClient;
use worker::env::FromEnv;

type Error = Box<dyn std::error::Error + Send + Sync + 'static>;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mailgun_client = MailgunClient::from_env()?;
    let lambda_handler = LambdaHandler::new(*mailgun_client);
    let handler = handler_fn(move |a, b| lambda_handler.handle_event(a, b));
    lambda_runtime::run(handler).await?;
    Ok(())
}

And we get the following error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/lambda-test.rs:13:57
   |
13 |     let handler = handler_fn(move |a, b| lambda_handler.handle_event(a, b));
   |                                                         ^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the body at 13:30...
  --> src/lambda-test.rs:13:30
   |
13 |     let handler = handler_fn(move |a, b| lambda_handler.handle_event(a, b));
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `lambda_handler`
  --> src/lambda-test.rs:13:42
   |
13 |     let handler = handler_fn(move |a, b| lambda_handler.handle_event(a, b));
   |                                          ^^^^^^^^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `impl Future` will meet its required lifetime bounds
  --> src/lambda-test.rs:14:5
   |
14 |     lambda_runtime::run(handler).await?;
   |     ^^^^^^^^^^^^^^^^^^^

We cannot puzzle this together. Here is the code for lambda handler, which I imagine is relevant:

use crate::mailer::{BatchSendRequest, BatchSendResult, Mailer};
use core::future::Future;
use lambda_runtime::{Context, Handler};
use serde_json::Value;

type Error = Box<dyn std::error::Error + Send + Sync + 'static>;

pub struct LambdaHandler<T: Mailer> {
    mailer: T,
}

impl<T: Mailer> LambdaHandler<T> {
    pub fn new(mailer: T) -> Self {
        LambdaHandler { mailer: mailer }
    }

    pub async fn handle_event(&self, event: Value, _: Context) -> Result<(), Error> {
        let batch_send_request: BatchSendRequest = serde_json::from_value(event)?;
        let result: BatchSendResult = self.mailer.send_mail(&batch_send_request).await?;
        println!("{:#?}", result);
        Ok(())
    }
}

Help will be much appreciated.

1 Like

The dereference on this line is suspicious:

let lambda_handler = LambdaHandler::new(*mailgun_client);

Does mailgun_client implement Copy, or how does that work? Why is there a reference here in the first place?

I'm no expert but don't you want to implement the Handler trait for LambdaHandler and then pass it directly to lambda_runtime::run?

From the documentation:

The mechanism available for defining a Lambda function is as follows:

Create a type that conforms to the Handler trait. This type can then be passed to the the lambda_runtime::run function, which launches and runs the Lambda runtime.

Hm.. Our idea was to implement various traits for MailgunClient that would create one using different ways of getting its configuration. In the example we use a FromEnv trait, which takes the configuration from environment variables. It looks like this:

pub trait FromEnv {
    fn from_env() -> Result<Box<Self>, Error>; 
}

I recall that without putting it in a box we had some errors about unknown size. Maybe we’re not getting out of the box correctly?

Okay so it's a Box. In this case, I would probably redefine the trait to this:

pub trait FromEnv: Sized {
    fn from_env() -> Result<Self, Error>; 
}

The sized error you got was because without the : Sized, someone could implement FromEnv from some weird type with unknown size, and then you can't have a method like from_env if it isn't boxed.

Anyway, this is unrelated to the error. I realized what it's complaining about. The lambda handler must return a future that does not have any references to the handler object, however the futures generated by an async fn that takes &self will store such a reference. Since the trait doesn't allow it, you get an error.

How to approach this depends a bit on what you have stored inside the LambdaHandler, and how you need to access it. Especially if you need mutable access.

One way to approach it is like this:

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mailgun_client = MailgunClient::from_env()?;
    let lambda_handler = Arc::new(LambdaHandler::new(*mailgun_client));
    let handler = handler_fn(move |a, b| {
        let lambda_handler_clone = Arc::clone(&lambda_handler);
        async move {
            lambda_handler_clone.handle_event(a, b)
        }
    });
    lambda_runtime::run(handler).await?;
    Ok(())
}

Here I use std::sync::Arc, which is a wrapper that overrides clone to something that gives out a new handle to the same shared object. The shared object stays alive until the last Arc to it is destroyed, and an Arc doesn't count as a reference so you can store it in the handler by cloning it. (the clone is very cheap — it just increments a counter for how many handles you've got to it)

The disadvantage of an Arc is that because it is shared, it does not allow mutable access to the value inside it. In your case, the handler takes &self so that's fine, but if you ever want to change that to &mut self, you can't when you use an Arc. (unless you add something like a Mutex inside the Arc)

1 Like

@lemmih you are correct, that seemed like a cleaner approach, but we had more troubles going down that road :stuck_out_tongue:. We did get a simpler version running with handler_fn, so we’re working our way up.

Ah, of course. This is definitely the way to go. Thanks!

No, we don’t need mutable access. All that’s in there are details for making authenticated requests to the API. Presumably we don’t need Arc::clone in this case?

If you want to use an Arc to avoid the lifetime problem, you'll need to clone the Arc (which is very cheap).

Yes that makes sense. What I guess I meant to ask was: is Arc the most straightforward solution, even if we don’t need mutable references? I mean, we could just clone the MailgunClient, right, but that is presumably more expensive, correct? Is Arc needed specifically, as opposed to Rc?

Yes, Arc is the most straight-forward solution for immutable access. In fact, Arc alone does not work if you need mutable access. You can't use Rc because it's single-threaded only.

As for cloning the MailgunClient, well that's usually not an option, but when it is, you could consider both. Using Arc is however a very robust choice.

async implies multi-threaded?

Thank you @alice ! Apropos, I just found this issue on the lambda_runtime project.

Now we have another compiler complaint (perhaps needs a new thread?) Is this related to the use of Arc?

error[E0271]: type mismatch resolving `<impl Future as Future>::Output == Result<_, _>`
  --> src/lambda-test.rs:21:5
   |
21 |     lambda_runtime::run(handler).await?;
   |     ^^^^^^^^^^^^^^^^^^^ expected opaque type, found enum `Result`
   | 
  ::: /home/altaurog/causematch/code/messaging-service/worker-lambda/src/lambda_handler.rs:17:67
   |
17 |     pub async fn handle_event(&self, event: Value, _: Context) -> Result<(), Error> {
   |                                                                   ----------------- checked the `Output` of this `async fn`, expected opaque type
   |
   = note: while checking the return type of the `async fn`
   = note: expected opaque type `impl Future`
                     found enum `Result<_, _>`
   = note: required because of the requirements on the impl of `Handler<serde_json::value::Value, _>` for `HandlerFn<[closure@src/lambda-test.rs:15:30: 20:6]>`

No it seems unrelated to the use of Arc. Can you post the updated code?

Here is a MNWE. (I contemplate opening a github issue on the aws-lambda-rust-runtime repo for help on this, but I am not sure if that is proper rust etiquette.)

use core::future::Future;
use std::sync::Arc;
use lambda_runtime::{Context, handler_fn, Error};
use serde_json::{json, Value};

// in real life, this holds some configuration
pub struct LambdaHandler {}

impl LambdaHandler {
    pub fn new() -> Self {
        LambdaHandler {}
    }

    pub async fn handle_event(&self, event: Value, _: Context) -> Result<Value, Error> {
        Ok(json!({"message": "OK"}))
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let lambda_handler = Arc::new(LambdaHandler::new());
    let handler = handler_fn(move |a, b| {
        let lambda_handler_clone = Arc::clone(&lambda_handler);
        async move { lambda_handler_clone.handle_event(a, b) }
    });
    lambda_runtime::run(handler).await?;
    Ok(())
}

I note that the shared resource example in the aws-lambda-rust-runtime codebase does not use Arc. Is the reason this works because SharedClient.name has 'static lifetime?

You need to add an await inside the async block

1 Like

Hurray! It builds! Thank you @alice!

1 Like

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.