4 ms·
Gaze upon our work, and rejoice: pub fn try_join<A, B, AR, BR, E>(a: A, b: B) -> impl Future<Output = Result<(AR, BR), E>> where A: Future<
by MrBuddyCasino 5y ago
Gaze upon our work, and rejoice:
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>>
I get why its like that, but I really wished this wasn't a typical Rust generics signature.
- jcelerier 5y agoGenerics are about encoding supplemental bits of information in the type system. That information has to be spelled out somewhere
- cormacrelf 5y agoYes, however in my experience you typically have to write out constraints like these a bunch of times. Refactoring a generic trait with 10 implementations is a nightmare. A struct with a couple of variations on the constraints for its impl blocks is also painful beyond like one generic param. It has to be spelled out somewhere, but then at minimum somewhere else, and in general about 3-4 more times. I see why Haskell folks like their type level functions.
- tinco 5y agoIn Haskell I used to do this trick where if I wanted to neatly type annotate, but my function had some crazy signature, I'd compile the program, but passing the function into another function in an intentional incorrect way. The compiler would complain "expected A but got B". And I would simply copy B and make it the type annotation :P 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.
- myrrlyn 5y agoyeah, bounds written on the type decl shouldn't be repeated on the impl. the current typeck is not smart enough to do that but the ever-"nearly there we promise" replacement is. it'll land one day. they promise
- Ygg2 5y agoDo you have a better way to write the same constraints? Conceivably if there was a way to not repeat the future parts.
- paavohtl 5y agoI don't think this is a _typical_ signature. On the contrary, it's quite exceptional. In my experience having more than 1 type parameter in a function is quite rare in most code, unless you're building an advanced, composable library like a futures implementation.
- MrBuddyCasino 5y agoI didn't mean "typical" as in "most Rust fn signatures are like that", obviously most functions are far simpler. Who would even claim that? 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.
- jen20 5y agoC#'s `Tuple<T1,T2,T3,T4,T5,T6,T7,TRest>` [1] would like a word! [1[: https://docs.microsoft.com/en-us/dotnet/api/system.tuple-8?view=net-5.0 https://docs.microsoft.com/en-us/dotnet/api/system.tuple-8?v...
- richardwhiuk 5y agoYou can do: 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>> {
- diragon 5y agoRust truly is a better C++.
- drran 5y agoIt's easy to read, isn't? 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.
- scoutt 5y ago> It's easy to read, isn't? 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.
- gpderetta 5y agoTo be fair the full expansion of A and B are literally on line below.
- kmeisthax 5y agoSo, there's a few things to point out here: - 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.
- magicalhippo 5y agoI don't know any Rust, and that thing made perfect sense to me (compared to other things in Rust). How would you have preferred it looked?