The context: I'd like to let user code define a closure that can execute operations on an LDAP server. I want to hide the opening/closing of the underlying connection.
Unfortunately, I can't make my code compile because I'm hitting some lifetime issues and I don't know how to resolve them.
use crate::{error::Error, CrateResult};
use ldap3::{Ldap, LdapConnAsync, LdapConnSettings, Scope, SearchEntry};
use std::future::Future;
struct LdapQuery {
ldap: Ldap,
}
impl<'a> LdapQuery {
async fn create_connection() -> CrateResult<Ldap> {
// ...
Ok(ldap)
}
pub async fn get_email(&mut self, username: &str) -> CrateResult<String> {
// ...
Ok("user@email.io")
}
pub async fn execute<T, F, Fut>(f: F) -> CrateResult<T>
where
F: FnOnce(&Self) -> Fut,
Fut: Future<Output = CrateResult<T>>,
{
let mut ldap = Self::create_connection().await.unwrap();
let result = f(&Self { ldap: ldap.clone() }).await;
ldap.unbind().await.unwrap();
result
}
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn ldap_closure() -> CrateResult<()> {
dotenv::dotenv().ok();
let result = LdapQuery::execute(|x| async {
let mut emails = Vec::new();
emails.push(x.get_email("user_1").await.unwrap());
emails.push(x.get_email("user_2").await.unwrap());
Ok(emails)
})
.await
.unwrap();
println!("result: {:?}", result);
Ok(())
}
}
The current error is returning this value requires that '1 must outlive '2. I tried to annotate execute (wrongly), but to no avail.
error[E0596]: cannot borrow `*q` as mutable, as it is behind a `&` reference
--> src/ldap.rs:91:25
|
91 | emails.push(q.get_email("user_1").await.unwrap());
| ^ cannot borrow as mutable
error[E0596]: cannot borrow `*q` as mutable, as it is behind a `&` reference
--> src/ldap.rs:92:25
|
92 | emails.push(q.get_email("user_2").await.unwrap());
| ^ cannot borrow as mutable
error: lifetime may not live long enough
--> src/ldap.rs:89:45
|
89 | let result = LdapQuery::execute(|q| async {
| __________________________________________--_^
| | ||
| | |return type of closure `impl warp::Future` contains a lifetime `'2`
| | has type `&'1 ldap::LdapQuery`
90 | | let mut emails = Vec::new();
91 | | emails.push(q.get_email("user_1").await.unwrap());
92 | | emails.push(q.get_email("user_2").await.unwrap());
93 | | Ok(emails)
94 | | })
| |_________^ returning this value requires that `'1` must outlive `'2`
|
= note: requirement occurs because of a mutable reference to &ldap::LdapQuery
= note: mutable references are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
error[E0373]: async block may outlive the current function, but it borrows `q`, which is owned by the current function
--> src/ldap.rs:89:51
|
89 | let result = LdapQuery::execute(|q| async {
| ___________________________________________________^
90 | | let mut emails = Vec::new();
91 | | emails.push(q.get_email("user_1").await.unwrap());
| | - `q` is borrowed here
92 | | emails.push(q.get_email("user_2").await.unwrap());
93 | | Ok(emails)
94 | | })
| |_________^ may outlive borrowed value `q`
|
note: async block is returned here
--> src/ldap.rs:89:45
|
89 | let result = LdapQuery::execute(|q| async {
| _____________________________________________^
90 | | let mut emails = Vec::new();
91 | | emails.push(q.get_email("user_1").await.unwrap());
92 | | emails.push(q.get_email("user_2").await.unwrap());
93 | | Ok(emails)
94 | | })
| |_________^
help: to force the async block to take ownership of `q` (and any other referenced variables), use the `move` keyword
|
89 | let result = LdapQuery::execute(|q| async move {
| ++++
is a problem, as the future of such a function usually depends on the lifetime (i.e. calling F with &'a Self returns a different future type than calling it with &'b Self, etc..)
Compare this thread
there's a workaround (first answer in that thread) that doesn't support closures though (due to current limitations of the rust compiler), so it's not useful for your caseEdit: actually, your closure does not capture anything, to it is technically an option; still it's probably too annoying to use in production;
or it’s possible to work with (pinned) Box<dyn Future<...>> (second answer in that thread).
In your case, it also seems reasonable to pass the Self by-value, i.e.
Indeed, my closure does not capture anything yet. The idea is then to capture a Vec<String> of usernames.
I had tried what you proposed (FnOnce(Self), by-value) but it still does not compile :
error[E0373]: async block may outlive the current function, but it borrows `q`, which is owned by the current function
--> src/ldap.rs:89:51
|
89 | let result = LdapQuery::execute(|q| async {
| ___________________________________________________^
90 | | let mut emails = Vec::new();
91 | | emails.push(q.get_email("user_1").await.unwrap());
| | - `q` is borrowed here
92 | | emails.push(q.get_email("user_2").await.unwrap());
93 | | Ok(emails)
94 | | })
| |_________^ may outlive borrowed value `q`
The thing is I'm not sure it is the right way to use an async block in this situation.
As you can see, I'm still not at ease with Rust harder concepts
I will look at the second answer in the thread you mentioned now.