I'm not a Rust expert by any means, but I'm surprised to hear this. In my Rust code, doing anything with a database connection is not at all different from, say, Go or TypeScript.
For example, I use the deadpool-postgres crate for database pooling. Getting a connection looks like this:
let conn = self.pool.get().await?;
Because of RAII, you don't need a higher-order function helper, but if you really wanted to make one:
async fn with_conn(
&self,
f: impl AsyncFnOnce(Object) -> Result,
) -> Result {
let conn = self.pool.get().await?;
f(conn).await
}
Now you can do:
with_conn(|conn| async move {
conn.query("SELECT 1") // Or whatever
}).await
If you know TypeScript, this shouldn't be too difficult to read or write. The gnarliest stuff here is knowing the type signature of the function argument; because of async, it must be AsyncFnOnce, for one, and you need to know that the type that the deadpool crate returns is called Object (which doesn't sound like a connection, to be fair). Determining the exact concrete type to match type constraints on is sometimes a chore, but TypeScript is surely no different here!
If you don't know Rust too well, the "move" part will be a little mysterious, to be sure.