Comment by atombender

2 days ago

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<T>(
        &self,
        f: impl AsyncFnOnce(Object<Manager>) -> Result<T>,
    ) -> Result<T> {
        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<Manager> (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.

Just investigated -- looks like this works now! Yay!

For this family of examples, had been completely stymied by AsyncFnOnce not being released yet. IIRC it had been in the works for several years, was still an experimental feature when I was trying to use it, and I gave up after much frustration at trying to get a version of Rust with experimental features working under devenv (nix).

A subtraction then to my frustrations with Rust -- though I'd still be very wary of doing this, having seen how fragile higher-order functions have been in the past.

  • That explains it. I think async closures were stabilized a year ago. Before that, you'd have needed to write out the async signature as non-async with futures (that's what async is syntactic sugar for, anyway). Something like:

        f: impl FnOnce(Object<Manager>) -> impl Future<Output = Result<T>>