pub fn - public function
<...> - declaration of types,
A - type of first future,
B - type of second future,
AR - type of result of first future
BR - type of result of second future
E - type of error
So, public function try_join accepts two futures, which my return <AR>esult and <BR>esult or <E>rror, and returns future which will return tupple with (<AR>, <BR>) or <E>rror.
pub fn try_join<A, B, AR, BR, E>(a: A, b: B) -> impl Future<Output = Result<(AR, BR), E>>
where
A: Future<Output = Result<AR, E>>,
B: Future<Output = Result<BR, E>>,
Come on... "A", "B", "E", "a", "b"??? Naming stuff like this is the standard way? I mean, if I know nothing about what the code does, and I find this kind of function, I'll start swearing right away.Also, the function only does:
TryJoin::Polling {
a: State::Future(a),
b: State::Future(b),
}
Which is clearer and shorter. Why even writing a function signature like that? It's longer and more complicated to understand than the code itself! I don't know much Rust, but since a semicolon is missing this is just a return value, right?So why not?
let res = TryJoin::Polling {
a: State::Future(fetch_thing("first")),
b: State::Future(fetch_thing("second")),
}.await?
PS: I don't want to criticize, and I'm not a Rust literate, I'm just amazed with its complexity.- The example you gave at the bottom won't really work, because enum variants aren't fully-fledged types on their own and they can't impl `Future`. Therefore, you can't `.await` them on their own. Furthermore, the point of `try_join` is to poll both futures until one fails, which you can't do with `async` syntax.
- Rust requires that you spell out all generic parameters like this at function boundaries. It's technically possible to infer types across such boundaries, but that has the potential for significant changes to the typing of a function to go entirely unnoticed. In fact, I believe some of Rust's documentation specifically calls out problems Haskell had with inferring function signatures like this.
That being said, you are correct that we could do this instead:
```pub fn try_join<FutureA, FutureB, ValueA, ValueB, Error>(future_a: FutureA, future_b: FutureB> -> impl Future<Output = Result<(ValueA, ValueB), Error> where FutureA: Future<Output = Result<ValueA, Error>>, FutureB: Future<Output = Result<ValueB, Error>>,```
That is slightly more readable.
At this point, somehow, I'm starting to consider C++ templates beautiful! Perhaps a signature form like:
template <FutureA, FutureB, ValueA, ValueB, Error>
pub fn try_join() ... etc.
would be clearer at this point.No, it's verbose and ugly. Easy to read would be something like:
fn try_join(a:A:Future<E||AR>,b:B:Future<E||BR>) -> impl Future<E||(AR,BR)>
(Rounding to the nearest Rust-like syntax where appropriate and borrowing `type (||) = Either` from Haskell.)I meant "you will only encounter this level of generics complexity in Rust" (disclaimer: I don't know C++). They can't be that rare either, I have not written a lot of Rust but already encountered similar constructs multiple times. But admittedly that might be partly due to no-std requirements.
[1[: https://docs.microsoft.com/en-us/dotnet/api/system.tuple-8?v...
I see why Haskell folks like their type level functions.
Nowadays the editor would help you do that. When I'm coding Rust the editor shows the types inline in a small font, which is very helpful.
How would you have preferred it looked?
pub fn try_join<A: Future<Output = Result<AR, E>>, B: Future<Output = Result<BR, E>>, AR, BR, E>(a: A, b: B) -> impl Future<Output = Result<(AR, BR), E>> {