6 ms·
I'm a giant Rust fanboy and have been since about 2016. So, for context, this was literally before Futures existed in Rust. But, I only work on Rust code spora
by ragnese 6y ago
I'm a giant Rust fanboy and have been since about 2016. So, for context, this was literally before Futures existed in Rust.
But, I only work on Rust code sporadically, so I definitely feel the pros and cons of it when I switch back and forth to/from Rust and other languages.
The problem, IMO, isn't about allocations or ownership.
In fact, I think that a lot of the complaints about async Rust aren't even about async Rust or Futures.
The article brings up the legitimate awkwardness of passing functions/closures around in Rust. But it's perfectly fair to say that idiomatic Rust is not a functional language, and passing functions around is just not the first tool to grab from your toolbelt.
I think the actual complaint is not about "async", but actually about traits. Traits are paradoxically one of Rust's best features and also a super leaky and incomplete abstraction.
Let's say you know a bit of Rust and you're kind of working through some problem. You write a couple of async functions with the fancy `async fn foo(&x: Bar) -> Foo` syntax. Now you want to abstract the implementation by wrapping those functions in a trait. So you try just copy+pasting the signature into the trait. The compiler complains that async trait methods aren't allowed. So now you try to desugar the signature into `fn foo(&x: Bar) -> impl Future<Foo>` (did you forget Send or Unpin? How do you know if you need or want those bounds?). That doesn't work either because now you find out that `impl Trait` syntax isn't supported in traits. So now you might try an associated type, which is what you usually do for a trait with "generic" return values. That works okay, except that now your implementation has to wrap its return value in Box::pin, which is extra overhead that wasn't there when you just had the standalone functions with no abstraction. You could theoretically let the compiler bitch at you until it prints the true return value and copy+paste that into the trait implementation's associated type, but realistically, that's probably a mistake because you'd have to redo that every time you tweak the function for any reason.
IMO, most of the pain isn't really caused by async/await. It's actually caused by traits.
- vmchale 6y ago> The article brings up the legitimate awkwardness of passing functions/closures around in Rust. That's a hard problem when you have linear/affine types! Closures don't work so neatly as in Haskell; currying has to be different.
- jstrong 6y agoalso a long-time rust user, and I buy this. one of the things it took me longest to realize when writing rust is to reach for traits carefully/reluctantly. they can be amazing (e.g. serde), but I've wasted tons of time trying to make some elegant trait system work when I could have solved the problem much more quickly otherwise.
- ragnese 6y agoExactly. Which is unfortunate, because the fact that Rust has true type classes is absolutely awesome. But when dealing with traits, you have to remember the orphan rules, and the implicit object-safety rules- which sucks, because you might not have planned on using trait objects when you first defined the trait, but only tried to do so later. Async definitely makes it even more painful.
- mathw 6y agoI wonder if most of the pain is actually caused by Rust async being an MVP, so things like async trait functions (which would be very nice) don't exist... yet. I don't know if anybody has shown that they can't ever exist, it's just that they weren't considered necessary to get the initial async features out of the door. Rather like how you can't use impl Trait in trait method signatures either (there's definitely some generics-implications-complexity going on with that one).
- pas 6y agoThis macro goes a very long way toward solving the problem: https://github.com/dtolnay/async-trait https://github.com/dtolnay/async-trait