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.