CAN return value referencing function parameter from function, but not closure?

I'm getting the classic error[E0515]: cannot return value referencing function parameter whatever, and while I very much understand what's wrong with the trivial example given by the compiler:

fn get_dangling_reference() -> &'static i32 {
    let x = 0;
    &x
}

I do not understand why my code yields this error when using a closure with map, but NOT when using the same value (conn, in the below example) without a closure. Of note, I am using the Rocket framework, and the Outcome<S,E,F> type is described here: rocket::outcome - Rust and defined here: Outcome in rocket::outcome - Rust . The problem occurs when using Outcome::map which takes a closure having one parameter of type S. It does NOT occur when I unwrap and use S directly:

/// validate permissions of OnBehalfOf
/// by checking against database agents/principals
async fn validate_obo_permissions<'r>(
    _req: &'r Request<'_>,
    obo: &OnBehalfOf,
) -> Result<bool, AuthorizationError> {

    // Request guard yields Outcome<AppdataDb, _, _>
    let maybe_conn = AppdataDb::from_request(_req).await;

    // async closures are unstable hence we await later
    /*
    maybe_conn.map(|conn| {
        conn.run(move |c| { // `conn is borrowed here`
            AgentPrincipal::validate_pair(agent_id, principal_id, c)
                .map_err(|_| AuthorizationError)
        })    // -> Future<Result<bool, some_Error>, _>
    })  // `returns a value referencing data owned by the current function`
    .success_or(AuthorizationError)
    .map_err(|_| AuthorizationError)?.await
    */
    // `error[E0515]: cannot return value referencing function parameter `conn``

    // The below strategy of unwrapping `maybe_conn` works fine, even when I "return"
    // a value from the function referencing `conn`
    match maybe_conn {
        Outcome::Success(_) => {},
        Outcome::Failure(_) => { return Err(AuthorizationError); },
        Outcome::Forward(_) => { return Err(AuthorizationError); },
    };
    let conn = maybe_conn.unwrap();

    let principal_id = obo.id;
    let agent_id = obo.agent_id;
    
    conn.run(move |c| {
        AgentPrincipal::validate_pair(agent_id, principal_id, c)
    }).await
    .map_err(|_| AuthorizationError)
}

As noted in the code, the first, commented-out strategy (functional approach) yields E0515, complaining that conn is borrowed at the indication location (I get this) and "returns a value referencing data owned by the current function". I THINK I get this, and importantly here the "return" is the closure, correct?

In any case, the lower code block (procedural strategy), which unwraps maybe_conn works fine, even though I am ALSO returning from this function a value referencing conn.

Is there a way I am missing to make the first strategy work -- alternatively, what am I missing?

Thanks so much in advance

Follow-up question: would this work if I could use async closure block and await earlier (i.e., immediately after conn.run), to eliminate the situation whereby the map's closure has the conn parameter outlive it in the form of the Future ?

What are the exact function signatures of the AppdataDb::from_request(), AppdataDb::run(), and AgentPrincipal::validate_pair() methods, and the types of the id and agent_id fields of OnBehalfOf? The types involved can be important when finding the cause of lifetime errors.

Ah, thank you for reminding me to include this essential info facepalm

  1. from_request

AppdataDb::from_request is automatically derived by the #[database(type)] macro (database in rocket_sync_db_pools - Rust ; macro code and generated fn body here: Rocket/database.rs at 4c6c0b497c78ebe72a48542a2ed18ff1acfee108 · SergioBenitez/Rocket · GitHub ) and satisfies implementation of the FromRequest trait: FromRequest in rocket::request - Rust . Ultimately, the type signature is:

async fn from_request<'life0, 'async_trait>(
        request: &'r Request<'life0>
    ) -> Pin<Box<dyn Future<Output = Outcome<Self, Self::Error>> + Send + 'async_trait>>
    where
        'r: 'async_trait,
        'life0: 'async_trait,
        Self: 'async_trait;
  1. run

AppdataDb::run() is also added automatically by rocket code generation ( macro code and fn body: Rocket/database.rs at 4c6c0b497c78ebe72a48542a2ed18ff1acfee108 · SergioBenitez/Rocket · GitHub) and has signature:

async fn run<F, R>(&self, __f: F) -> R
            where
                F: FnOnce(&mut diesel::PgConnection) -> R + Send + 'static,
                R: Send + 'static,

(function body is so simple I am also including it here: self.0.run(__f).await

  1. validate_pair

This is mine, rather than the framework's, and is of course the simplest of the three.

fn validate_pair(
        agent: i32, principal: i32,
        connection: &diesel::PgConnection,
    ) -> Result<bool, diesel::result::Error>
  1. The id fields in OnBehalfOf are all (both) i32.

Okay, managed to find two solutions. The problem is that the Future contains an &mut conn borrow, but conn is dropped at the end of the map(), before we can await the future. The first solution is to use as_mut() to only take conn by reference:

async fn validate_obo_permissions<'r>(
    req: &'r Request<'_>,
    obo: &OnBehalfOf,
) -> Result<bool, AuthorizationError> {
    let mut maybe_conn = AppdataDb::from_request(req).await;

    let principal_id = obo.id;
    let agent_id = obo.agent_id;

    let result = maybe_conn
        .as_mut()
        .map(|conn| {
            conn.run(move |c| {
                AgentPrincipal::validate_pair(agent_id, principal_id, c)
                    .map_err(|_| AuthorizationError)
            })
        })
        .success_or(AuthorizationError)?;
    result.await
}

The second solution is to move conn into our own Future so that it lasts long enough:

async fn validate_obo_permissions<'r>(
    req: &'r Request<'_>,
    obo: &OnBehalfOf,
) -> Result<bool, AuthorizationError> {
    let maybe_conn = AppdataDb::from_request(req).await;

    let principal_id = obo.id;
    let agent_id = obo.agent_id;

    maybe_conn
        .map(|conn| async move {
            conn.run(move |c| {
                AgentPrincipal::validate_pair(agent_id, principal_id, c)
                    .map_err(|_| AuthorizationError)
            })
            .await
        })
        .success_or(AuthorizationError)?
        .await
}

And for reference, here's how I've been testing it:

/* Cargo.toml:
[dependencies]
diesel = "1.4"
rocket = "0.5.0-rc.1"
rocket_sync_db_pools = { version = "0.1.0-rc.1", features = ["diesel_postgres_pool"] }
*/

use diesel::PgConnection;
use rocket::{request::FromRequest, Request};
use rocket_sync_db_pools::database;

struct AgentPrincipal;

impl AgentPrincipal {
    fn validate_pair(
        agent: i32,
        principal: i32,
        connection: &PgConnection,
    ) -> Result<bool, diesel::result::Error> {
        let _ = (agent, principal, connection);
        unimplemented!()
    }
}

#[database("")]
struct AppdataDb(PgConnection);

struct AuthorizationError;

struct OnBehalfOf {
    id: i32,
    agent_id: i32,
}

async fn validate_obo_permissions<'r>(
    req: &'r Request<'_>,
    obo: &OnBehalfOf,
) -> Result<bool, AuthorizationError> {
    todo!()
}
2 Likes

Aha, thanks! I think this is what I suspected when I asked "Follow-up question: would this work if I could use async closure block and await earlier (i.e., immediately after conn.run ), to eliminate the situation whereby the map 's closure has the conn parameter outlive it in the form of the Future ?" -- I was not able to phrase it as elegantly as you.

Side note, before posting, I had already tried async move |conn| but this did not work because async closures are unstable; however I did not grasp the semantic difference between an async closure, and a closure that runs an async block (your solution #2) as described here [1]. I was so close!

Thanks again; I am really appreciative of you working through this with me.

[1] rust - What is the difference between `|_| async move {}` and `async move |_| {}` - Stack Overflow

I somehow missed a post above and so I'm just rehearsing that 🤦

So here we have a conn.run(…) yielding a Future / .awaitable / lazy object which is borrowing from conn until until fully .awaited, but since conn is owned inside the .map() closure, it will be dropped when such .map() ends.

You have two solutions:

  • do not own conn in the closure, by not giving ownership of maybe_conn to .map():

    - maybe_conn.map(|conn| {
    + maybe_conn.as_ref().map(|conn| {
    

    (or .as_mut())

  • Return a future that owns the given conn, even if the .run() "constructor" just uses a borrow (note that this solution is not generally applicable to other patterns, only tot futures thanks to them being allowed to be self-referential).

    The key idea here is a that a_future and async move { a_future.await } are kind of The Same Thing™.

    So, from

    { let future = conn.run(…); future }
    

    you can do:

    { let future = conn.run(…); async move { future.await }}
    

    And now, since the issue is that the obtained future is not owning conn, the trick is to expand the scope of the async block:

    - {
    + async move {
          let future = conn.run(…);
    -     async move {
              future.await
    -     }
      }
    

    That way, rather than async move(future) {, we are expressing async move(conn)

    This yields:

    maybe_conn.map(|conn| async move {
        conn.run(move |c| {
            AgentPrincipal::validate_pair(agent_id, principal_id, c)
                .map_err(|_| AuthorizationError)
        }).await
    })
    .success_or(AuthorizationError)
    .map_err(|_| AuthorizationError)?
    .await
    
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.