Hacker News new | ask | show | jobs
by josephg 702 days ago
I look forward to your next post on the topic then!

> I'm curious why you needed to deal with `Pin` instead of using async functions.

The protocol I was trying to implement streams messages over time over a single HTTP request thats kept alive for a long time. This is how Server-Sent Events (SSE) works, and its how Google Chat in gmail was first implemented in a way that supported IE 5.5 (!!!).

This was a couple years ago now, so the details are a bit fuzzy. And I was relatively new to rust at the time. I was, at the time, still sometimes surprised by the borrow checker.

My goal was to make a writable async stream that I could push messages into from other parts of my program. And it also needed backpressure. When you sent messages into the stream, the protocol implementation it encoded them and streamed them into the body of my HTTP response object. I was (I think) using hyper.

This is before TAIT was in rust, and for one reason or another I needed to store / reference the future object I was making. (If you use an async fn(), you don't get a name for the Future type the function returns. So I couldn't put the return type in my struct, because I couldn't name it.)

So I ended up writing a custom struct that implemented Future, so I could reference the future elsewhere in my code. Hence, implementing Poll myself. I can't honestly remember how Pin came into it all. I think hyper's API for doing this sort of thing stored a Pin<T> or something.

I remember at some point trying to write a where clause using higher ranked type bounds to describe the lifetime of a future object that was- or wasn't- associated with the lifetime of the corresponding HTTP request. And that may or may not have been Pinned, and I gave up.

It might be fun to revisit this at some point now rust's async support has matured a little. And now that I've matured a lot in how I understand rust. I certainly don't imagine that everyone using async will run into the sort of quagmire that I hit. But this was the first thing I ever really wanted to do with async rust, and it felt horrible to fall on my face trying.

2 comments

Thanks for your write up. This sounds like a perfect use case for async generators (which yield many times and compile to Stream instead of Future), a feature I hope Rust will gain in the next year or two. To receive messages from other parts of the program, I would have the async generator hold the receiving end of a channel.
Yeah, I would love generators (and async generators) to make it into stable rust. They would be a godsend for my work.
When I worked on Fuchsia and we had a heavily async set of system interfaces, users went through this learning path very regularly, and it was very painful for many. Folks who reached out for help early in their first engagement on this kind of path got help and following a "learn by doing" started to understand what was going on after a few iterations of the same challenge. Those who struggled trying to figure it out all on their own had a really awful time and in one example even went back to c++ for a sizable project because of the wall they ran into. There's a big gap here for folks who want to self-help their way through this. TAIT reduces the number of cases that come up, but there are still plenty.

Reflecting on a point from the article, it's possible that the ?Move being required in every declaration might have been better on this aspect. The point here about not being able to remember where the requirement to deal with Pin comes from is an indicator: the virality of the key traits involved, along with implicit implementation is a particularly tricky mix, it leads to action at a distance, which is also why first time engagements are so hard for users. Mix in some misunderstandings and you're in nope territory.

Thanks for saying so. Its nice to know I'm not alone in struggling with this stuff. Now that some time has passed, I might loop back and take another stab at it. It'd feel great to clear this hurdle.