6 ms·
> The thing I really want to try and get across here is that *Rust is not a language where first-class functions are ergonomic.* So... don’t use first-class fu
by ironmagma 6y ago
> The thing I really want to try and get across here is that *Rust is not a language where first-class functions are ergonomic.*
So... don’t use first-class functions so much? It’s a systems language, not a functional language for describing algorithms in CS whitepapers. Or use `move` (the article does mention this).
There are easy paths in most programming languages, and harder paths. Rust is no exception. The fact that passing around async closures with captured variables and also verifying nothing gets dropped prematurely and without resorting to runtime GC is bleeding-edge technology, so it should not be surprising that it has some caveats and isn’t always easy to do. The same could be said of trying to do reflection in Go, or garbage collection in C. These aren’t really the main use case for the tool.
- TheCoelacanth 6y agoYeah, a struct holding four different closures is not a pattern that makes much sense in Rust. That would look a lot better as a Trait with four methods.
- lmm 6y ago> So... don’t use first-class functions so much? It’s a systems language, not a functional language for describing algorithms in CS whitepapers. Then maybe it was a mistake to adopt an async paradigm from functional languages that relies heavily on the idea that first-class functions are cheap and easy? (FWIW I think Rust was right to pick Scala-style async; it's really the only nice way of working with async that I've seen, in any language. I think the mistake was not realising the importance of first-class functions and prioritising them higher)
- ironmagma 6y agoIt's always possible there's a better way. If you know of one, maybe you can write a proposal? There is always Rust v2. Rust lacks a BDFL and so it's almost like the language grows itself. Chances are, the async model that was used was picked because it was arrived upon via consensus.
- lmm 6y agoI don't think there is a better approach; I think the right thing would have been to lay proper functional foundations to make async practical. I did speak against NLL at the time, and I keep arguing that HKT should be a higher priority, but the consensus favoured the quick hack.
- ragnese 6y agoI agree that Rust should have embraced HKTs instead of resisting them every step of the way. It's been my observation that the Rust lang team is pretty much against HKTs as a general feature, which makes me sad. I'm also happy to meet the only other person who had reservations about NLL! :p I have mixed feelings about it, still. It really is super ergonomic and convenient, but I really value language simplicity and NLL makes the language more complex. I also don't think there's much Rust can do differently in regards to "functional foundations". With Rust's borrow/lifetime model, what would you even do differently with respect to `fn`, `Fn`, `FnMut`, and `FnOnce`?
- steveklabnik 6y agoHKTs were considered, and do not actually solve the problem, in Rust. Rust is not Haskell. The differences matter. Nobody is against HKTs on some sort of conceptual level. They just literally do not work to solve this problem in Rust.
- ragnese 6y agoWhich problem are we referring to? I was only making a general statement that I think Rust would have benefited from HKTs instead of doing umpteen ad-hoc implementations of specific higher-kinded types. I'm far from an expert, so please correct me if I'm wrong: Wouldn't HKTs help us abstract over function/types more easily, including closures? Aren't GATs a special case of HKTs? If Rust somehow had HKTs and "real" monads, we could have a do-notation instead of the Try trait+operator and Future trait+async+await, right? I'm not saying that HKTs would fix (most of) the issues mentioned in the article around tricky closure semantics and whatnot.
- stormbrew 6y ago> maybe it was a mistake to adopt an async paradigm from functional languages > I think Rust was right to pick Scala-style async I'm confused by this assertion. I'm more aware of procedural language origins of syntactic async/await than functional? The scala proposal in 2016 for async/await even cites C#'s design (which came in C# 5.0 in 2012) as an inspiration[1]. From there, it appears python and typescript added their equivalents in 2015 [2]. If anything, async-await feels like an extremely non-functional thing to begin with, in the sense that in a functional language it should generally be easier to treat the execution model itself as abstracted away. [1] https://docs.scala-lang.org/sips/async.html https://docs.scala-lang.org/sips/async.html [2] https://en.wikipedia.org/wiki/Async/await https://en.wikipedia.org/wiki/Async/await
- dwohnitmok 6y ago> If anything, async-await feels like an extremely non-functional thing to begin with Futures/promises (they mean different things in different languages), like many other things, form monads. In fact async-await is a specialization of various monad syntactic sugars that try to eliminate long callback chains that commonly affect many different sorts of monads. Hence things like Haskell's do-notation are direct precursors to async-await (some libraries such as Scala's monadless https://github.com/monadless/monadless https://github.com/monadless/monadless make it even more explicit, there lift and unlift are exactly generalized versions of async and await). To see how async-await might be generalized, one could turn to various other specializations of the same syntax, e.g. an async that denotes a random variable and an await that draws once from the random variable. To see the correspondence with a flatMap method (which is the main component of a monad), it's enough to look at the equivalent callback-heavy code and see that it looks something like Future(5) .flatMap(x -> doSomethingFutureyWithX(x) .flatMap(lookItsAnotherCallback) )
- stormbrew 6y agoI'm not clear on if this is supposed to be disagreement or elaboration or education. The fact that in a language like Haskell, you can perform something like async-await with futures (which are absolutely a kind of monad) in a natural way is precisely what I had in mind with what you quoted. Regardless, the specific heritage of async-await syntax seems rooted in procedural languages (that do borrow much else as well from functional languages, yet are still not functional in any meaningful sense) like C# and python. They are absolutely an attempt to bring some of the power of something like monadic application (including do notation) into a procedural environment as an alternative to threads (green or otherwise), which hide the execution state machine completely.
- kelnos 6y ago> Rust was right to pick Scala-style async Huh, I actually find Rust and Scala to do async quite differently. The only thing in common to me is the monadic nature of Future itself. Otherwise I find there to be a big fundamental difference in how Scala's async is threadpool/execution-context based, while Rust's async is polling-based. Then there are the syntactic differences around Rust having async/await and Scala... not.
- marshray 6y ago> maybe it was a mistake to adopt an async paradigm from functional languages I always thought of async as higher language sugar for coroutine patterns that I'd seen in use in assembler from the 1980's.
- de6u99er 6y agoWanted to write something similar, just not as elaborate as you did (native German speaker here). Another good example is Java, and languages running on top of the JVM.
- gkya 6y ago> It’s a systems language, not a functional language for describing algorithms in CS whitepapers. This kind of toxicity is why I left programming behind as a career.
- ironmagma 6y agoHow is saying Rust is one thing but not another thing toxic? I never said it’s the author’s fault Rust is broken or anything like that. It just has some goals, and being a functional programming language isn’t one of them (as far as I know).
- steveklabnik 6y agoOne way to read you comment, which maybe you didn't intend, is "this is a language for Real Work, not one for those silly academics." I don't personally think you went that far, but I imagine that is how the parent read it. I think it's the "for" to the end of the sentence that does it.
- ironmagma 6y agoThat would make sense. On the contrary, though, I quite admire whitepapers’ use of FP and would like to learn it someday. But my understanding is that there are already quite a few languages devoted to that, and Rust’s focus is something different. After all, if you have the same goals as another language, you just may end up re-creating the same language with different syntax. That said, it would be nice if someday Rust could be as convenient to write FP in as say Lisp or Haskell.
- viktorcode 6y agoI think your implication that a system programming language can't have nice stuff is based on system languages of old. Unless a language is absolutely must have manual memory allocations, it can have 1st class functions and closures.
- ironmagma 6y agoI never implied it can’t be nice, just that it currently isn’t and that a solution to this blog post is, “don’t do that, at least for now.” I mean, the title is that async Rust doesn’t work, yet I use several successful codebases that use it, so it actually does work, just not the way the author wants it to. Another solution could be to submit a proposal to improve async support in the desired way.