Programming

RUST RANT #2 ASYNC CLOSURES

Started by Paris · Jun 3, 2026

#10949
I got way more messages about how people just don't like rust than I was expecting to have today. Don't validate my feelings. It's my fuck ups but it still hurts every single time I see it. We want to be correct here.

While I'm saying that here is a line of code from Test4Pay I've been working on today.

F: FnMut(AcquiredArtiSender, bool) -> Pin<Box<dyn Future<Output = RetryAttempt<T>> + Send + 'a>>


Let me outline what this SINGLE LINE OF CODE is doing.

F is a trait bound in a where clause. It says this generic type "F" must satisfy everything to the right of the colon. This F is a type of per attempt closure being passed into a retry function the builds the request and sends it.

FnMut(AcquiredArtiSender, bool) says that F must be a closure callable MULTIPLE TIMES with a mutable state. I need to use FnMut here because the retry loop could invoke the closure twice. One for the send another for a retry.

On each call it receives two arguments:

- AcquiredArtiSender which is a freshly acquired connection sender for the attempt that is passed BY VALUE so the closure can take ownership of the connection for that specific attempt.

- bool which is a retry flag telling the closure if this request is a retry (we only retry once)

now the piece that is fucking mind numbing for me. Because this is async rust.

-> Pin<Box<dyn Future<...>>> is needed because the closure doesn't return a plain value. It returns a fucking future! Let's break it down now...

dyn Future<...> is a type erased future. The concrete future type is generated by each async move {} block that is anonymous and unnameable. We hide it behind a dyn.

Box<dyn Future<...>> dyn is unsized! So it must be behind a pointer. We heap-allocate it in a box. Is this effective? Maybe?? It costs a small allocation but fuck everything we do it anyway because I'm tired.

Pin<Box<...>> AND again I mention this is a fucking future. We pin this before they can be polled because it MAY hold self references across different await points. If you studied rust async you would see Pin<Box<dyn Future>> as the standard boxed future shape.

And done... oh wait no there is more.

Future<Output = RetryAttempt<T>> is saying when the future completes it resolves to a RetryAttempt<T> which is a enum that signals if it is done or not sent (if the request was not provably put on the wire, so loop and retry). T is a generic which serves buffered and streaming call sites.

+ Send being that this is a boxed future you must send to move across threads. The whole reason I needed all this was so I can send this shit. Blame Tokio's multi-threaded runtime for this monstrosity of code.

+ 'a is a lifetime bound. Saying the future may borrow data that lives for how long this lives for. This is important because the compiler said it is. This single concrete lifetime is tied to the enclosing call and not a higher ranked one. This allows the compiler to prove send and stop screaming at me.

Now guess what I wanted to write and did write at the VERY BEGINNING.

F: AsyncFnMut(AcquiredArtiSender, bool) -> RetryAttempt<T>

But I couldn't do that because you can't write an async closer to return future's auto-traits directly in stable rust. Why can't I require a hidden future to be Send? Because the compiler is dumb and wants to make me cry. There is literally no syntax I can use in rust to express a future's own auto-traits. At least none that I fucking know.

I cry. I feel like an idiot writing this stuff.
#10950
↳ Replying to @Paris
Go to bed lol the 'a will still hate you in the morning :))
#10951
↳ Replying to @M1000
I'm going to have nightmares. Literal nightmares over this shit.
#10952
↳ Replying to @Paris
It's what happen when you wake up and choose the borrow checker lmao.
#10953
↳ Replying to @Paris
the language is cool but I just cant describe it
#10955
↳ Replying to @Paris
YOU 🫵 are NOT fucking up

it is rust 🥰

man I am happy I didn't touch rust.

fr though goodluck with test4pay
#10957
↳ Replying to @Paris
Precise yet short. Beautiful.

This is important because the compiler said it is.


Sums up the whole Rust learning process perfectly.