4 ms·
I've been spending a good majority of my work hours with Rust and especially writing network services using Tokio. When things work there, it is very reliable,
by pimeys 9y ago
I've been spending a good majority of my work hours with Rust and especially writing network services using Tokio. When things work there, it is very reliable, fast and has a tiny footprint with resources.
Now reading the promise to get a stable async/await and also having Tokio 0.2 in the pipeline, I'd say async Rust might be ready for prime time.
Don't get me wrong here, I enjoy using Tokio, but I'd say if you're not very experienced yet with Rust, and you try to mix an asynchronous database query and a web request together in the same event loop, or you wanted to use a reference to `self` in an inner future (which might suddenly want a static lifetime), you quite easily end up into trouble... Trouble like a couple of pages of errors, that are just not readable at all. And you get out maybe by knowing to use a `map_err` in that one future call after the second `then`, or by not referencing `self` in your futures...
Now when reading the announcement, and I've read and understood the RFC's, the next generation of async Rust will help here tremendously. And I will definitely be amongst the first ones adding the changes to my code, to finally make it cleaner and nicer for the next developer to jump in.
- harpocrates 9y agoYou've changed my mind on adding special syntax for `async`, generators, and results (`?`). I previously thought it was a big mistake given that these all generalize as monads (granted, there is some ground to be covered before such an abstraction would fit in Rust). What I hadn't considered: specialized syntax leads to better error messages for the most common cases. That's probably quite a good thing, especially since descriptive error messages make me much more productive. (Not to mention that specialized syntax also acts as a type annotation of sorts too; a common problem I have in Haskell is figuring out _which_ monad a certain `do` block is using.)
- gilmi 9y ago> a common problem I have in Haskell is figuring out _which_ monad a certain `do` block is using. Does type signatures not help in that case?
- harpocrates 9y agoYes, when people use them. There is no really elegant way of adding a type annotation to a `do` block. (do x <- pure 1 y <- pure 5 pure (x + y)) :: Maybe Int It isn't uncommon to have to scan the `do` block for some statement that constrains the monad somehow. This is a known and discussed problem, so I'll refer you to the article usually reference around this issue: https://wiki.haskell.org/Do_notation_considered_harmful https://wiki.haskell.org/Do_notation_considered_harmful
- nogenerics123 9y agoMany times do blocks are passed into functions so the monad type can be looked up from the function definition. flip runStateT s $ do ... To find the type of this do block I don't have to scan it I just have to look up the definition of runStateT. If I have to add an explicit annotation to a do block it's simple enough to separate it into another function. justSix :: Maybe Int justSix = do x <- pure 1 y <- pure 5 pure (x + y) I read the article and couldn't find the part where they said that type inference is a problem with do notation.
- Rusky 9y ago> (granted, there is some ground to be covered before such an abstraction would fit in Rust) I would claim that such an abstraction is fundamentally incompatible with Rust. Not only does Rust lack the means to write a Monad trait on which to build do-notation, but even given HKT there's no single type signature that the various monad instances would fit. `Result` and `Option` are type constructors while `Iterator` and `Future` are generic traits; some instances require `Fn` or `FnMut` while others require `FnOnce`. And even assuming those problems could be solved, the way do-notation interacts with control flow is not composable with idiomatic Rust. Nested closures prevent the use of return/break/continue and imperative loops; Haskell doesn't have those features so people just lift their functional counterparts into monads. Monad transformer stacks make the situation even worse- they are a pain to compose even with each other, let alone imperative code. If you want a unifying mechanism for this stuff in Rust it's gonna need to be fundamentally more powerful than monads. Scoped continuations, maybe? Certainly nothing that looks like >>=.
- andrewaylett 9y agoYou don't need do-notation to use Monads, that's just useful for building up a lazy-evaluated data-structure describing what IO to perform. Java and Javascript, for example, have been gradually introducing monadic concepts into their ecosystems. Java collections and Futures grow 'of' and 'flatMap', JS Promises aren't quite purely monadic but have 'resolve' and 'then'. So it's perfectly possible for an imperative language to use monadic features to implement async operations. But much as I enjoy using JS Promises (really!) I prefer ES6 async/await, even though it's just syntactic sugar. Not least because even fairly straightforward-looking async/await code can desugar to something you wouldn't want to try to read by hand.
- Rusky 9y agoI'm... not sure what you're getting at here. Rust is chock-full of "monadic concepts" like that, including the entire sets of `Iterator` and `Future` combinators. (Note that even without do-notation these have the composability problems I mention- you can't, for example, `break` out of a loop from within a `.then` callback; you have to break the loop down and reimplement it yourself with recursion. This is what the async/await "sugar" helps with- makes normal control structures compose with async code.) What I'm talking about is a single Monad abstraction to tie these all together. I mention do-notation because it's one example of something you would built on top of such an abstraction, and one way to bring together the seemingly-disparate collection of `?`/`yield`/`await!`. In Haskell, for example, you can use do-notation not only for IO, but also for async code, and list comprehensions, and early returns, and a host of other things.
- jadbox 9y agoIt would be nice to see examples of these new systems hypothetically working together... what does an async DB access within a web request look like with async/await+tokio 0.2?
- pimeys 9y agoPretty much like your good old sync code. Some examples how it might look like you can read from the async/await macro crate [0], somebody might be able to provide more recent examples... [0] https://github.com/alexcrichton/futures-await https://github.com/alexcrichton/futures-await
- conradev 9y ago> Trouble like a couple of pages of errors, that are just not readable at all This is indeed very frustrating. I've had to learn to translate between "what the compiler is telling me" and "what the issue actually is".
- teacpde 9y agoIs that because async is involved? I have minimal experience with async Rust code, but my experience with Rust compiler errors has been superb so far. The compiler always tells me exactly where things went wrong and the error messages are quite informative.
- steveklabnik 9y agoYes, but also no. That is, async is done via futures, which means that you're composing a lot of futures together, which means that you have a long type signature, which means that if you get a type mismatch, you get quite a long error, where 99% of the type is the same.