News for hackers. Stuff that matters.
Re:The Rust I wanted had no future by sph on Monday June 05, 2023 @04:03AM (#36193721)
Very interesting insight from Graydon, in hindsight I too would have loved something more towards ML than C++. I never liked the kitchen sink approach that I see first C++, now Rust moving towards, but I respect what Rust has managed to solidify into. It's a good language.

That said, I still hate async with a passion, it makes the language more complex and not very elegant (i.e. function coloring). And now that I know how it works behind the scene (thanks to Jon Gjengset [1]), it feels so complicated and hacky, a mediocre very high level concept that someone managed to implement as a zero-cost abstraction. Impressive, but still a bad idea.

I'm sure the pro of having a BDFL instead of a committee is being able to follow a singular vision, instead of trying to appease members by adding the fad du jour which might stray a little too far from the original vision. Too many chefs in the kitchen and all.

1: https://www.youtube.com/watch?v=ThjvMReOXYM

Re:The Rust I wanted had no future by TurboHaskal on Monday June 05, 2023 @04:13AM (#36193771)
> I too would have loved something more towards ML than C++

I stopped paying attention to the language around 2012-2013 or so when the direction clearly steered towards the latter.

You may want to check https://austral-lang.org/ out.

Re:The Rust I wanted had no future by dorian-graph on Monday June 05, 2023 @06:45AM (#36194624)
I was about to ask if there's any "Rust by ML" languages out there. Thank you.
Re:The Rust I wanted had no future by noelwelsh on Monday June 05, 2023 @10:01AM (#36196279)
O'Caml is heading in that direction. Jane Street are adding more control over memory layout and allocation, and multicore finally arrived recently.
Re:The Rust I wanted had no future by gautamcgoel on Tuesday June 06, 2023 @03:40AM (#36209491)
Lol there's no apostrophe in OCaml - it's not an Irish name like, say, O'Brian.
Re:The Rust I wanted had no future by noelwelsh on Tuesday June 06, 2023 @03:52AM (#36209587)
You're rude and you're ignorant of OCaml's history. There is no apostrophe in OCaml now, but there used to be. E.g. https://web.archive.org/web/20050207032955/http://www.ocaml....

OCaml is / was short for Objective Caml. Hence the name used to have an apostrophe. At some point that was dropped.

Re:The Rust I wanted had no future by bpbp-mango on Monday June 05, 2023 @09:00AM (#36195586)
null type :(
Re:The Rust I wanted had no future by TylerE on Monday June 05, 2023 @03:10PM (#36201724)
Nim is the closest to what I had originally hoped Rust would be. Especially Nim 2 with the new GC is really exciting. I suspect it will outperform all but the best hand tuned allocators.

It's even hard real time capable, which makes it potentially viable in scenarios like games where GC traditionally hasn't been.

Re:The Rust I wanted had no future by pornel on Monday June 05, 2023 @04:32AM (#36193862)
Function coloring article conflated two things: one was legitimate limitation of JS unable to wait for async result from sync call, and the other was just author's opinion how the syntax should look like.

Rust's async doesn't have the limitation author described – you can spawn and block both (Tokio has some limits there, but that's Tokyo's choice, not language limitation), making "color" largely irrelevant.

The second point was that both should have identical syntax, which Rust deliberately chose not to, because from Rusts perspective that would be too much implicit magic.

Re:The Rust I wanted had no future by sph on Monday June 05, 2023 @04:40AM (#36193895)
I still disagree a little. You can call an async function in Rust from a regular one, that's true, but the returned value still needs to be passed through an executor to be useful.

Async without function coloring means being able to call `async add(a, b: usize) -> usize` and having an usize back whether you're calling from a regular or async function. If you need slightly different logic because of async, it is indeed function coloring.

In fact, a hypothetical async without coloring would not even need the `async` keyword at all, as all function would be effectively the same so could be called asynchronously or not. I'm not sure that is even possible with this abstraction.

Re:The Rust I wanted had no future by creatonez on Monday June 05, 2023 @04:53AM (#36193974)
Thing is, it's a function coloring that mostly doesn't impede you by making certain operations impossible without massive refactoring. Regardless of an extra method call needed, it gets rid of the "you can only call a red function from within another red function" barrier by allowing you to do it, as long as you specify how (execute inside this thread, or delegate to a worker thread). This makes most of the issues associated with Javascript function coloring evaporate.
Re:The Rust I wanted had no future by sph on Monday June 05, 2023 @05:00AM (#36194014)
Fair enough. It's not a deal-breaker kind of coloring, my issue is mostly conceptual. It's simply not elegant as a construct, but perhaps it doesn't have to be.
Re:The Rust I wanted had no future by johnnyanmac on Saturday June 10, 2023 @07:22AM (#36269562)
My experience with asynchronous programming is brief, but it taught me that you will never have elegant parallelism. It's a completely different problemset, you need a completely different solution to handle it. But the ideal API wants to treat the problems as one in the same.

So the two camps I've seen on solving it is either

a) blackbox it and set some rules for implementers, inevitably leaving to some hidden and very nasty bugs on the user end or bottlnecks for advanced users, or

b) give implementers the full tools to setup, launch, and synchronize themselves, which may end up being slower than a single threaded solution if the user isn't adept already with parallel programming.

You inevitably always have bits of a) creep in, even for the most explicit solutions

Re:The Rust I wanted had no future by ohgodplsno on Monday June 05, 2023 @05:12AM (#36194075)
Not knowing about function coloring is a stupid dream and has terrible implications on the performance of your code, which is infinitely more important than you losing 2 minutes having to figure out that you really want to `runBlocking { callThatBlocksForADamnLongTime() }`.

The insight that function coloring gives you is terribly important. Roman Elizarov (of Jetbrains, author of Kotlin and lead on Kotlin Coroutines) agrees too: https://elizarov.medium.com/how-do-you-color-your-functions-...

async functions without coloring means that the only warning you'll ever get that `calculate2Plus2()` actually ends up running a distributed BigQuery and writing the end result to disk, printing it to stdout() and parsing that result to give it back to you is... hopefully, documentation is up to date and you read it?

Async function coloring is not a problem. Async function coloring is a solution to "software developers are awful and will do awful things without any warning". If `calculate2Plus2` did not exhibit the write to disk behaviour in v1.0.0, but bumping to v1.0.1 does, I'd really want a warning that it does at least, and ideally a compiler error. The proper solution to function coloring is to have a pleasant API to interop between both worlds so that, at worst it's just a dozen characters more to say "yep, I really want to block here".

Re:The Rust I wanted had no future by mike_hearn on Monday June 05, 2023 @05:31AM (#36194170)
Async doesn't solve that problem. What you're asking for is some sort of expression in the type system of expected performance, but there's no tech that can do this. Consider that modern NVMe SSDs can do disk reads faster than many calculations over the content of what was just read. A function that changes its algorithm to have worse time complexity won't be marked as async but could break your app, adding a single disk read would require marking as async but probably won't break your app (especially if it's in a non-hot path), adding 10,000 more reads where previously you had one doesn't require changing your async annotation but might break your app, etc.

So the whole sync/async distinction doesn't really make much sense outside the context of single threaded UI toolkits, where indeed you have to read the docs anyway because doing some very CPU intensive work on the UI thread will block it even if no "blocking" APIs are ever invoked (and what is or is not blocking is somewhat arbitrary anyway in many cases).

Re:The Rust I wanted had no future by hayley-patton on Monday June 05, 2023 @06:09AM (#36194389)
There are type systems which can track complexity (e.g. http://gallium.inria.fr/~fpottier/slides/fpottier-2011-01-jf...) but that's still different to performance; async/sync tells us things like "touches disk" or "touches network" but not "O(2^n) might take a really long time".

Alternately, an effect system can communicate "touches disk", can make functions which are polymorphic to effects (e.g. map touches the disk if the mapping function touches the disk), and can distinguish different effects, unlike async/sync being anything-or-nothing. IMO async is more of an implementation detail (as "may suspend/yield" is not a very precise or interesting effect), whereas effects would communicate the properties that ohgodplsno wants to know.

Re:The Rust I wanted had no future by mike_hearn on Monday June 05, 2023 @06:33AM (#36194549)
Yes, but are there any effects systems in use outside of maybe Haskell? Effects seem mostly to be stuck in the research lab and have been for a long time.
Re:The Rust I wanted had no future by hayley-patton on Monday June 05, 2023 @06:37AM (#36194566)
I don't mean "effects" in the sense where you can have effect handlers which get delimited continuations and all; it'd just mark what could happen (like checked exceptions). But I can't think of any languages with that and not effect handlers; Koka, Eff, and Unison come to mind for effects, though their practical-ness may vary.
Re:The Rust I wanted had no future by zozbot234 on Monday June 05, 2023 @07:09AM (#36194761)
Do you really need effect handlers as a separate feature once you have async/await? It seems like you could easily encode one with the other.
Re:The Rust I wanted had no future by hayley-patton on Monday June 05, 2023 @09:48AM (#36196159)
An effect handler is allowed to invoke a continuation multiple times; I believe this is not possible with async/await.
Re:The Rust I wanted had no future by ohgodplsno on Monday June 05, 2023 @07:13AM (#36194792)
Verse has effects, which by virtue of exposing it to the entire Fortnite community probably makes it a bigger language than Haskell in terms of user counts.

https://dev.epicgames.com/documentation/en-us/uefn/specifier...

converges/diverges, computes, varies, reads, writes, transacts, no_rollback.

Re:The Rust I wanted had no future by ohgodplsno on Monday June 05, 2023 @06:21AM (#36194481)
Adding a disk read won't break my software, but it can certainly make my software non-portable. If it suddenly expects a disk write and I'm running on a ROM, I'm going to have a hard time. If it suddenly tries to contact google.com and I'm on an airgapped machine, I'm going to have a hard time. It's not even a matter of breaking the app actually, it's a matter of warning that "I'm going to go outside the realm of just your CPU and your memory". Sure, you could also mark as as async a function that adds two numbers. It's stupid, it's a JS thing to do, but you could. Except that you need to actively opt-in to being async in this case, as opposed to have it be forced upon you because you used a function that is also async.

Blocking has meaning in a lot more contexts, and being a consultant on JVM related topics, you should know that: it's the entire purpose of Project Loom, and Loom doesn't entirely get rid of colour coding either explicitly for that purpose. Loom wasn't made because the guys at Oracle have a deep love for JavaFX, but rather takes into account the server world, where you really want to know that you're going into another context, another computer, etc. The only time where the existence of async doesn't make sense is if your entire language and ecosystem expects everything to already be distributed. In which case, you've just switched the default color to async.

Finally, you chose to read async as the current JS/C# abomination implementation, but most of the sensible languages have implemented it as an effect: Kotlin has suspend funs, they don't return a Promise, but they tell you two things: they're going to touch something like the disk or the network, and if you really want to have them in a non-suspend context, you can either get a Deferred<T> out of them (and find another thread to run it on, and handle synchronization yourself), or run them on the current thread (and block everything).

Re:The Rust I wanted had no future by mike_hearn on Monday June 05, 2023 @06:31AM (#36194539)
Blocking in Loom is primarily about waiting for network traffic, possibly with extensions to file IO in future - it doesn't suspend if you do file IO today. Loom does get rid of coloring, or rather, doesn't introduce more of it and lets you phase out what exists, so I'm not sure what exactly you mean by that.

Kotlin suspend funs do not tell you they're about to touch network disk, that's the reason they use "suspend" and not "async". Suspend funs can also use generators (with yield) in which case no I/O is happening but they are nonetheless suspending.

So this is why blocking as a concept isn't a great one, IMO. The Kotlin designers were right to not use the word in their implementation of coroutines. Where Loom discusses blocking a bit, it's not defined as being about blocking, it's about being able to scale up threads to way beyond what was previously possible. It just so happens that the primary reason you'd want to do that is if you have lots of threads that spend lots of time waiting for things, but that doesn't automatically require network or disk access. For example you can use threads that spend all their time in Thread.sleep if you were writing an agent simulation.

Re:The Rust I wanted had no future by kaba0 on Monday June 05, 2023 @09:59AM (#36196259)
> possibly with extensions to file IO in future - it doesn't suspend if you do file IO today

Wasn’t basically all of the JDK’s file APIs rewritten to io_uring-like calls to support Loom? I have thought that IO was definitely something that Loom handled.

Re:The Rust I wanted had no future by mike_hearn on Monday June 05, 2023 @01:05PM (#36199390)
Last I heard it was on the todo list, but maybe it got done since I last looked. You can do async IO without io_uring.
Re:The Rust I wanted had no future by coldtea on Monday June 05, 2023 @09:04AM (#36195620)
>Adding a disk read won't break my software, but it can certainly make my software non-portable. If it suddenly expects a disk write and I'm running on a ROM, I'm going to have a hard time. If it suddenly tries to contact google.com and I'm on an airgapped machine, I'm going to have a hard time. It's not even a matter of breaking the app actually, it's a matter of warning that "I'm going to go outside the realm of just your CPU and your memory". Sure, you could also mark as as async a function that adds two numbers. It's stupid, it's a JS thing to do, but you could.

You're conflating too many orthogonal things.

It's not merely the performance of touches disk/network that async was used for those, it's because that waiting is not because you're held up by the language doing calculations. That isn't the case with a function like you describe.

Marking functions async when they aren't yielding just to signal that they might be slow is a bizzare idea. That's not what async is and it doesn't bring any real benefit, it's just abusing the notion (and limiting the contexts where you can run those functions). You'll still be using libraries which wont follow this strange idea, and you should know their performance characteristics.

Blocking code exists in all major languages, including JS. In a single thread context, having something "async" wont help you at all, if it calls anything blocking, which can be something as common as JSON.parse

>If it suddenly expects a disk write and I'm running on a ROM, I'm going to have a hard time. If it suddenly tries to contact google.com and I'm on an airgapped machine, I'm going to have a hard time.

All of those have nothing to do with async, and what async is created and used for.

What you want is something like Haskell's IO "tainting", a (side) effects system, or something to that (no pun intended) effect.

Re:The Rust I wanted had no future by lmm on Monday June 05, 2023 @07:34PM (#36205873)
This is a perfect solution fallacy. Yes, "whether this function will suspend (and shift execution onto a different thread)" does not tell you absolutely everything about that function. But it tells you things that are worth knowing, that you want to be visible (not super intrusive, but visible) in your IDE. And in a language with a decent type system the cost is pretty small; functions that are agnostic about whether they will run as async can and should simply be polymorphic in async-ness.
Re:The Rust I wanted had no future by anonymoushn on Monday June 05, 2023 @08:31AM (#36195315)
This comment reads like it's from an alternate dimension where "calling read(2) marks functions as async" is a common language feature. Over here, read(2) can block forever and normal functions can call it in just about every language.
Re:The Rust I wanted had no future by belmont_sup on Tuesday June 06, 2023 @10:29AM (#36213348)
I agree that hiding coloring entirely is really a trap. I want to know when some IO work runs because it means potential exceptions unknown with unpredictable latency in my program.

As a design choice of OCaml’s eio - an async runtime, you must pass around a dependency to all your IO functions. With the “net” object passed visible in my function type signature, I can tell some network work may occur. The first benefit is we get synchronous Go-like code.

Re:The Rust I wanted had no future by kaba0 on Monday June 05, 2023 @05:13AM (#36194082)
Virtual threads (like java’s loom, erlang’s and goroutines) are arguably that.

In case of java there really is no compiler magic even, “just” runtime magic.

Re:The Rust I wanted had no future by pas on Monday June 05, 2023 @04:43PM (#36203318)
how big is the overhead for this? and how much easier is working with virtual threads than Rust's async?
Re:The Rust I wanted had no future by winrid on Monday June 05, 2023 @05:34PM (#36204081)
1. Low enough that you don't need to worry for most web backend code and 2. Anything is easier than that. *XYZ is not Send* amiright?

With Rust's async you have to worry about a different worker thread picking up the work after a context switch, which makes things complicated. Not so with Java.

Re:The Rust I wanted had no future by josephg on Monday June 05, 2023 @06:09AM (#36194394)
Its certainly possible with compiler magic. Zig does exactly that - where functions can be colorless and they work slightly differently in async and syncronous contexts, doing the obvious thing in both cases. I'm not sure how well it works in practice (I haven't spent enough time with zig to know). I'd worry that bugs would creep in to whichever variant of the function you aren't exercising.

Its a lovely design though.

https://kristoff.it/blog/zig-colorblind-async-await/

Re:The Rust I wanted had no future by svieira on Monday June 05, 2023 @11:24AM (#36197571)
_Unyielding_ describes the issues that arise when the author of the code doesn't have control of the transactionality / atomicity of their operations at the level they need to: https://glyph.twistedmatrix.com/2014/02/unyielding.html

To my mind it's very much a balancing act between "low power to the developer, high power to the language" and "high power to the developer, low power to the language" all up and down the stack from software / hardware to "consumer / framework".

Re:The Rust I wanted had no future by svieira on Monday June 05, 2023 @05:35PM (#36204104)
See also: https://www.tedinski.com/2018/11/06/concurrency-models.html which is the first article I've ever seen that says that `m:n`-style-green-threads are bad and good at the same time.

TL;DR - he says that "doParallel" and "doConcurrently" are separate operations with distinct semantics that designers of programs must care about and that conflating the two (especially the common "doConcurrently-and-often-but-not-always-in-parallel") is one of the most common causes of bugs in programs that need to make progress in multiple threads of execution.

Re:The Rust I wanted had no future by astrange on Monday June 05, 2023 @09:24PM (#36206867)
A notable and common thing about this article is that it confidently declares futures/promises are good for parallelism, but this is wrong - they are flawed because they introduce priority inversions.

To schedule tasks properly, you need to know who is waiting on them as early as possible. With a promise, you only know when you get to the "wait()" call, which is too late.

The correct solution is called structured concurrency.

Re:The Rust I wanted had no future by svieira on Tuesday June 06, 2023 @02:20PM (#36216978)
Depends on if you're using Monix/Haskell style tasks (lazy, run when depended on) or JS-style promises (E Promise / C# Task / Java Future - run when instantiated, waiting is post-submission). Agreed completely though, structured concurrency is fantastic.
Re:The Rust I wanted had no future by xmcqdpt2 on Monday June 05, 2023 @07:19AM (#36194829)
You can do it using a runtime with scheduling and fibers.

The issue is that you want to be able to save the entire thread stack cheaply so that you can switch to another task at an async yield point, and then go back to your previous stack frame. You want to do this without spawning system threads.

Say you have an f -> g -> h call stack that blocks on IO. If all the functions are in fact async state machines (or some other kind of callbacks) your thread stack will look like this:

Executor.loop() -> Executor.run(h) -> h.await()

That is, your functions are really objects that are queued up and taken as needed by the executor. If h has a yield point, you can put it back on the queue, go back to the loop and pick up some other work. Later you can do the above again and await h to again up to the next yield point.

Now consider the case where the middle function g is NOT async. In that case the call from f to g will be a normal function call that gets put on your call stack. In a language where this is allowed, you'll still have to wait or execute h and you'll get a call stack that looks like,

Executor.loop() -> Executor.run(f) -> f.await() -> g() -> h.awaitUntilCompletion()

(where our awaitUntilCompletion is minimally just calling h.await in a loop until h is finished). At this point, you are stuck: you can't yield from g() because it is a normal function with a normal stack frame, so your thread has to wait till g() finishes before it can be used for anything else. You are basically blocking a thread on h. At this point you might spawn a new thread to keep your thread pool count up (which is expensive and memory hungry) or just accept the forced synchronous blocking (which reduces throughput).

If this happens over and over again, you either end up running out of memory or end up running code in a synchronous fashion but with worse performance (because of all the state machines). This is why functions are usually coloured, so that you don't get yourself in this accursed state by accident. AFAIK the alternative is to allocate all of your call stacks in the heap so that you can switch them in and out of kernel threads, which is what fibers are. This requires a complicated runtime with a scheduler such as in Loom,

https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html

Re:The Rust I wanted had no future by belmont_sup on Monday June 05, 2023 @07:54AM (#36195040)
Algebraic effects like how OCaml’s now implementing it may be possible! The ecosystem around effects in OCaml is still really young, but here’s an example of an http request being made that is asynchronous, non-blocking but looks synchronous with no special syntax.

https://github.com/mirage/ocaml-cohttp/blob/16e991ec1f7e5f0c...

Performing these effects is similar to throwing exceptions up the callstack where whichever ancestor handles the IO work, then resumes the child with IO work done in hand.

Re:The Rust I wanted had no future by mjmahone17 on Monday June 05, 2023 @08:50AM (#36195483)
As someone who likes OCaml, but hasn’t touched OCaml in ~5 years, that’s a very hard example to read. I can tell it’s making a network request, but I have no clue:

- what the type of res is (is it a string? A buffer? Async string? Something else?)

- how this code does not block: what is happening in parallel? At which point will it block?

I would like to be excited about OCaml’s algebraic effects, but right now I don’t really get it.

Re:The Rust I wanted had no future by yawaramin on Monday June 05, 2023 @11:29AM (#36197675)
- It's some type from which you can read a response body. In OCaml we often work in terms of abstract types i.e. the operations which can be done on types. If you want to see the exact name of the type you can always look up the signature of `Client.get`: https://github.com/mirage/ocaml-cohttp/blob/16e991ec1f7e5f0c... (nowadays LSP support in any good editor will show the type on hover)

- It's implementing non-blocking I/O using effect handlers. The complexity is not exposed to the library user, which is actually the whole point. If you want to dive into the concurrency library (Eio) and study how it works, that's very doable, just like any concurrency library in any other language.

There's really not that much to get–OCaml will do async I/O without having to colour functions, just like Go, Erlang/Elixir, and soon Java. It's not something sexy like monads that excite people with mysterious FP concepts, it's just a lot of hard work that went into simplifying how developers code at scale.

Re:The Rust I wanted had no future by belmont_sup on Tuesday June 06, 2023 @10:21AM (#36213233)
Yawar’s explained it all, but I’ll share tidbits of my own.

When reading ocaml on _text_ like from GitHub, I personally accept that I won’t need to know the type and underlying data structures just yet unless I read and have open the .mli file.

I’m excited first and foremost because we can have async code feel synchronous like Go.

How does the code run? IO runtime is defined in userland, like Rust’s Tokio. That’s the “Eio_main.run” part. Underneath, eio spins up and manages processes in a non blocking manner, even scheduling work.

Since effects are essentially resumable exceptions, this particular http request throws an effect to the runtime. The logic and this particular process pauses (okay I may be wrong here). Logic flow now continues somewhere in our runtime level (the effect handler), where all the async IO work happens. The runtime resumes the child process and returns the http result back to the child via a “delimited continuation”.

Re:The Rust I wanted had no future by moth-fuzz on Monday June 05, 2023 @10:53AM (#36197008)
You can't evaluate a future in normal rust as there's no default executor, you need to pull in some library to even make blocking calls to async functions.

IMO, this is even worse than function coloring.

Re:The Rust I wanted had no future by zanellato19 on Monday June 05, 2023 @11:13AM (#36197362)
You can also make your own executor. That's exactly how it was intended.
Re:The Rust I wanted had no future by weinzierl on Monday June 05, 2023 @01:03PM (#36199361)
It's a bit strange indeed. On the other hand you can't allocate either if you don't have an allocator. The difference is just that the allocator is opt-out while the reactor and executor are opt-in with no formal default.
Re:The Rust I wanted had no future by jarjoura on Monday June 05, 2023 @01:41PM (#36199992)
It's the same in C++. The compiler and language design came first and then the library (aka executor) part comes next.

Rust has basically 2 executor libraries, tokio and async-std. It seems to me like tokio is solidifying as the executor of choice and it's only a matter of time before that design is baked into the std library.

Re:The Rust I wanted had no future by menaerus on Tuesday June 06, 2023 @04:25AM (#36209820)
In C++ there is a default executor that is part of the standard library and has been so since C++11, e.g. no external library is needed - std::async.

std::async is mostly "good enough" for ordinary cases where you only want to fire-and-forget and have no control over the underlying dispatching and scheduling algorithms. If you want something tweaked towards your use-case, and hence actual high-performance executor library, std::async is not gonna cut it. And that's where the "C++ executors" proposal come in.

Re:The Rust I wanted had no future by BatmanAoD on Friday June 09, 2023 @02:18AM (#36253993)
That's...not precisely true. The C++ standard doesn't specify how std::async works, and for a while GCC just ran the operation sequentially, and later both GCC and Clang launched new OS threads by default. https://stackoverflow.com/q/10059239/1858225
Re:The Rust I wanted had no future by moron4hire on Monday June 05, 2023 @01:33PM (#36199841)
I've been using C# and (Java|Type)Script pretty much since they were invented. Both use a function coloring async system. I don't know what it's like in Rust, but at least in the two examples with which I have experience with this apparently much-maligned system, I really don't get the complaints. Having to "color" functions really isn't that big of a deal.
Re:The Rust I wanted had no future by z3t4 on Monday June 05, 2023 @02:33PM (#36200880)
* * *
Re:The Rust I wanted had no future by davedx on Monday June 05, 2023 @05:36AM (#36194196)
That being said... python had a BDFL and look how that turned out.

I think designing and evolving any living programming language is just one of the hardest problems out there.

Incredible blog post indeed, was awesome to read it.

Re:The Rust I wanted had no future by sph on Monday June 05, 2023 @07:10AM (#36194769)
A dictator might just have terrible ideas.

His dislike for functional constructs didn't do Python any favour, but now that he's gone I see Python is adding the kitchen sink as well. I haven't kept up with the language since that pattern matching proposal.

Re:The Rust I wanted had no future by pclmulqdq on Monday June 05, 2023 @09:36AM (#36195993)
People who are deeply into programming languages seem to like FP too much. That and theories of type systems. Python is doing some great things, but also just fucking around a lot.
Re:The Rust I wanted had no future by imtringued on Monday June 05, 2023 @11:37AM (#36197848)
As someone who doesn't like Python. Python's problems have almost nothing to do the lack of functional programming. It does have the worst lambda syntax of any programming language but that is about it.
Re:The Rust I wanted had no future by alwaysbeconsing on Tuesday June 06, 2023 @11:28PM (#36222500)
> A dictator might just have terrible ideas.

If they're all terrible, then the language won't appeal to anyone and will die off naturally.

On the other hand if everyone gets to add their favorite terrible idea, there may still be enough non-terrible parts to appeal to enough people to keep momentum.

Re:The Rust I wanted had no future by bazoom42 on Monday June 05, 2023 @08:39AM (#36195373)
> That being said... python had a BDFL and look how that turned out.

One of the most popular and succesful languages, and a major force in AI innovation?

Re:The Rust I wanted had no future by viscanti on Monday June 05, 2023 @08:58AM (#36195567)
The py2 to py3 transition seems to have gone very poorly, although most of the pain from that is behind us now.
Re:The Rust I wanted had no future by thiht on Monday June 05, 2023 @09:21AM (#36195834)
Honestly I blame the community.

The transition was slow (Python 2 was supported in parallel to 3 for a long period of time so there was more than enough time to migrate), relatively easy to do, and brought huge benefits to the ecosystem.

It could have been done and forgotten in 2 years if some community members had not been dicks about it.

Re:The Rust I wanted had no future by svieira on Monday June 05, 2023 @11:32AM (#36197745)
It really seemed that the issue was mostly "Unicode is really hard if you haven't thought _deeply_ about it" because application code, library and framework code, _and_ "system" Python code had to go through several iterations of "here's how to do it right" before something reasonable came out that solved issues for most / all stakeholders. And the Swift / Raku people are still sitting on the sidelines smiling, knowing that there are a lot more "lenses" to view text through that Python doesn't make easy (but neither does Haskell, JS, or Java, so there's good company in good-enough-for-now land).
Re:The Rust I wanted had no future by imtringued on Monday June 05, 2023 @11:35AM (#36197798)
Most of the breaking changes did not justify upgrading.
Re:The Rust I wanted had no future by hajile on Monday June 05, 2023 @12:31PM (#36198782)
I blame a lack of tools.

JS devs have been transpiling for years now and it works.

Python should have released a conversion tool that could automatically convert most stuff and identify the stuff it couldn't convert. This kind of directed upgrade would have made the process much easier for developers to actually accomplish.

Re:The Rust I wanted had no future by LordDragonfang on Monday June 05, 2023 @01:16PM (#36199561)
I'm confused, because isn't that exactly what they did? What was wrong with 2to3?

https://docs.python.org/3/library/2to3.html

Re:The Rust I wanted had no future by hajile on Monday June 05, 2023 @02:37PM (#36200963)
It didn't work well at all in my experience. Given how much code was thrown out rather than ported, I'm guessing that I'm not alone.
Re:The Rust I wanted had no future by viscanti on Monday June 05, 2023 @12:52PM (#36199179)
> Honestly I blame the community.

Sure. The point is that a community can make things go poorly, with or without a BDFL.

Re:The Rust I wanted had no future by collinmanderson on Monday June 05, 2023 @01:34PM (#36199849)
> The transition was slow

Here's the timeline the way I think about it:

- 2008-2012 Python 3 becoming usable (byte formatting, six library)

- 2012-2016 Libraries (Django, etc) becoming compatible

- 2016-today Applications (Trac, Ansible, Chrome, etc) becoming compatible.

It took a lot of time for the ecosystem to be ready. Yes, there was a lot of stubbornness, but that's reality.

There are many things that could have been done to make the transition go smoother.

Re:The Rust I wanted had no future by jefftk on Monday June 05, 2023 @09:01AM (#36195601)
That was a seriously bad call, but it was a single one and he's made a ton of other decisions over the years.
Re:The Rust I wanted had no future by rirze on Monday June 05, 2023 @10:59AM (#36197092)
who's "he"?
Re:The Rust I wanted had no future by wez470 on Monday June 05, 2023 @11:28AM (#36197653)
Re:The Rust I wanted had no future by estebank on Monday June 05, 2023 @11:32AM (#36197747)
Guido van Rossum
Re:The Rust I wanted had no future by _joel on Monday June 05, 2023 @10:57AM (#36197065)
With a dodgy 2->3 migration pathway and far too many package managers.
Re:The Rust I wanted had no future by 015a on Monday June 05, 2023 @12:31PM (#36198792)
And despite that, still a top 3 most popular programming language on the planet. And another in that top 3 is the universally-derided Javascript.

There's a lesson in there to learn. Its unfortunate that the people who need to learn it most likely won't.

Re:The Rust I wanted had no future by LordDragonfang on Monday June 05, 2023 @01:14PM (#36199533)
It's important to note that JS has an unfair advantage in that if you want to write code that runs on the universal platform, you're forced to use either it or something that transpiles to it. Python reached where it was on merits alone.

(That said, JS is actually a very versatile and almost-great language, getting better all the time)

Re:The Rust I wanted had no future by timschmidt on Monday June 05, 2023 @01:30PM (#36199777)
Python reached where it is because university CS programs which had previously been teaching Java reached for a new language.
Re:The Rust I wanted had no future by LordDragonfang on Monday June 05, 2023 @07:44PM (#36205990)
Most universities I knew were still teaching Java well into python's rise to popularity a decade ago. Python got where it is by focusing on being simple, easy, and nice to use. Seriously take a look at PEP-20 sometime:

https://peps.python.org/pep-0020/

Things like "There should be one-- and preferably only one --obvious way to do it." just was such a breath of fresh air in language design.

Re:The Rust I wanted had no future by _joel on Tuesday June 06, 2023 @06:55AM (#36210828)
I take it they didn't extend that to the package managers :)
Re:The Rust I wanted had no future by lmm on Monday June 05, 2023 @07:49PM (#36206037)
Python took a clear turn for the worse when it stopped following the BDFL model.
Re:The Rust I wanted had no future by dist1ll on Monday June 05, 2023 @07:33AM (#36194911)
> That said, I still hate async with a passion, it makes the language more complex and not very elegant (i.e. function coloring)

Function coloring seems to come up a lot in these discussions, but I don't see a better way without providing a runtime. Could you propose an alternative approach to async, without sacrificing ability to write zero-overhead, high-performance bare metal systems?

Re:The Rust I wanted had no future by umanwizard on Monday June 05, 2023 @09:06AM (#36195638)
They could have simply not added async to the language. I was happily using rust before async and it was perfectly fine.
Re:The Rust I wanted had no future by hgomersall on Monday June 05, 2023 @10:19AM (#36196528)
So don't use it. Why object to those that do have a use case for it?
Re:The Rust I wanted had no future by ninkendo on Monday June 05, 2023 @10:34AM (#36196706)
Because it's possible the crate you want to use, uses it.
Re:The Rust I wanted had no future by hgomersall on Monday June 05, 2023 @12:25PM (#36198698)
Or alternatively the crate you want to use doesn't exist.
Re:The Rust I wanted had no future by umanwizard on Monday June 05, 2023 @10:48AM (#36196928)
Because I have to work with other people (both coworkers and developers of library crates).
Re:The Rust I wanted had no future by ay on Monday June 05, 2023 @10:55AM (#36197040)
fragmentation of the library ecosystem.
Re:The Rust I wanted had no future by logicchains on Monday June 05, 2023 @07:42AM (#36194971)
The Rust async story would be much nicer if they'd put in the hard work up front to support higher-kinded types, as then it could have a monadic async API like OCaml or Haskell. I don't know anyone who's used async in both Rust and Haskell who prefers the Rust approach. It'd also fix oddities like why it's possible to write a function like the following in C++ but not Rust:

    template<template<typename> class MyContainer>
    int getFirstInt(const MyContainer<int>& myContainer) {
      return myContainer[0];
    }
(In Rust it's not possible to write something like MyContainer<int>; only the int is allowed to be generic).
Re:The Rust I wanted had no future by chrismorgan on Monday June 05, 2023 @08:37AM (#36195363)
Are you aware of generic associated types, which landed in 1.65 (late last year)? To a considerable extent, they’re Rust’s answer to higher-kinded types. Less expressive in some ways, but fitting Rust way better, as basically a relaxation of a former restriction, rather than a new feature. If I’m reading your snippet right, GATs let you express exactly that, though minus `template`’s duck-typiness so you’d have to spell out what contract the container must adhere to.
Re:The Rust I wanted had no future by logicchains on Monday June 05, 2023 @11:28AM (#36197657)
>less expressive in some ways, but fitting Rust way better, as basically a relaxation of a former restriction, rather than a new feature

They're not expressive enough to implement a monadic async library, as far as I'm aware, so they don't fulfill the role of proper HKTs in this context.

>minus `template`’s duck-typiness so you’d have to spell out what contract the container must adhere to.

Do you think it's consistent that the T in MyContainer<T> can be "duck typed" but not the MyContainer?

Re:The Rust I wanted had no future by estebank on Monday June 05, 2023 @11:36AM (#36197825)
In Rust neither is duck typed. For T you must specify the traits it must have. This makes compilation fail at definition, not at the point of incorrect usage.
Re:The Rust I wanted had no future by zozbot234 on Monday June 05, 2023 @12:16PM (#36198516)
> They're not expressive enough to implement a monadic async library, as far as I'm aware

AIUI you need GC in order to do that. There's a reason why Rust's async/await support needs compiler magic.

(I'd like to see standard interfaces for "pluggable" garbage-collected arenas in Rust, but this will need to wait until after Allocators/Storages get fully stabilized.)

Re:The Rust I wanted had no future by littlestymaar on Monday June 05, 2023 @08:02AM (#36195109)
> (i.e. function coloring)

I really wish people stopped using this concept, especially in the context of Rust because the `async fn`/“regular function” split is strictly equivalent to the `try_something()`/`something()` split (the first one being failible and returning a `Result` in case of failure). `Result`s and `Option`s are coloring the stack in exactly the same way a `Future` does (and `async` is pure syntactic sugar on top of future).

So, someone may like exceptions and green threads more than `Result` and `async` (and this is a completely valid PoV, even though I personnaly like the explicitness better), but thinking `async` is somehow special is just a conceptual mistake.

BTW, in the original blog post the “red” functions were actually functions with a callback parameter, which is actually very different.

Re:The Rust I wanted had no future by chmod600 on Monday June 05, 2023 @03:00PM (#36201463)
There's consistent use of Result and Option in std, community agreement to do things that way, and it's pretty easy to use them.

Tons of stuff in std and many other crates don't use async. A lot of stuff is harder and more code to do async. A lot of it is much harder to read.

That's the difference.

Re:The Rust I wanted had no future by littlestymaar on Monday June 05, 2023 @04:54PM (#36203451)
> There's consistent use of Result and Option in std,

Well, there are dozens[0] of incompatible error types in std, two of them being just called `Error`[1][2].

Everytime you create a function that use more one of the different error types from std (for instance, an I/O error and a TryFrom-related error) you have to create a custom Error enum to accommodate the different kinds of errors from std…

> community agreement to do things that way

And the community agreement is e̶r̶r̶o̶r̶-̶c̶h̶a̶i̶n̶, ̶f̶a̶i̶l̶u̶r̶e̶, ̶ s̶n̶a̶f̶u̶, this_error, unless it's for a binary crate and then it's ̶a̶n̶y̶h̶o̶w̶, ̶e̶y̶r̶e̶, color-eyre.

The error ecosystem is much more fragmented than the async one (even though I think this_error is here to last, so I don't have to migrate once again…). In the async world, there's pretty much tokio (but I think it would actually be better if it interoperated better with other runtimes so alternatives could emerge)

> A lot of it is much harder to read.

Ah yes, fut.await is fundamentally harder to read than res?, right?

Overall, you vastly underestimate the friction that the `Result`-based error handling adds. I still think it's worth it, and it's been slowly getting better (did I say I loved this_error?), but it remained one of the biggest point of friction in Rust over the 8 years (time flies) I've been using it. Async is comparatively less disrupting (mostly because it only appears in the call stacks where you're doing I/O, whereas errors are ubiquitous).

[0]: https://doc.rust-lang.org/std/error/trait.Error.html?search=...

[1]: https://doc.rust-lang.org/std/io/struct.Error.html

[2]: https://doc.rust-lang.org/std/fmt/struct.Error.html

Re:The Rust I wanted had no future by osigurdson on Monday June 05, 2023 @09:21AM (#36195823)
I think a big part of any successful endeavour is alignment of authority, responsibility and competence. The problem with group decision making is responsibility is very diluted. If a bad decision is made, no one in particular is responsible.
Re:The Rust I wanted had no future by fzeindl on Monday June 05, 2023 @09:57AM (#36196223)
I hate async as well. Developers should have learned about communicating sequential processes and blocking queues and none of that would have been necessary. It creates a weird divide in every language. Just learn about threading and do it.

A nice talk about this: https://www.reddit.com/r/programming/comments/da141r/ron_pre...

Re:The Rust I wanted had no future by duped on Monday June 05, 2023 @11:13AM (#36197363)
Developers should learn the difference between concurrency and parallelism because async isn't equivalent to threading.
Re:The Rust I wanted had no future by Freedom2 on Monday June 05, 2023 @11:31AM (#36197710)
Yep, it's pretty alarming that a developer would toss away async when threading isn't a fit for all situations - what happened to the right person tool for the job? As a professional, I 'hate' some things too, but I'm not going to blindly make my work harder and less efficient out of some small preference I have.
Re:The Rust I wanted had no future by tomjakubowski on Monday June 05, 2023 @12:27PM (#36198732)
async has fragmented the crates ecosystem. If you want to want to write async-free code with threads, you'll probably still have to opt-in to an async executor for some dependency because these days many libraries will implement only an async interface
Re:The Rust I wanted had no future by duped on Monday June 05, 2023 @12:39PM (#36198945)
I think you're manifesting what I'm talking about. The reason you cannot replace async with threads has nothing to do with the ecosystem, but the simple fact that threading is a model of parallelism and async/await is a model of concurrency. These are two subtly different concepts in software architecture, but they are not the same thing.

More concretely, threaded code has nothing to say about whether or not its synchronous or asynchronous, and asynchronous code has nothing to say about whether or not it's evaluated in parallel.

Re:The Rust I wanted had no future by fzeindl on Monday June 05, 2023 @03:11PM (#36201760)
> I think you're manifesting what I'm talking about. The reason you cannot replace async with threads has nothing to do with the ecosystem, but the simple fact that threading is a model of parallelism and async/await is a model of concurrency.

That is an artificial distinction you made up. I have written many apps and desktop applications, all of which where using 3-10 threads, some of them doing "parallel" work meaning the parallel computation of divide and conquer algorithms, some were doing concurrent work.

I tried async often. I threw it away every single time, because it "infects" the entire codebase. I suspect the only reason async exists is because the Javascript event-loop is single-threaded and there are no blocking primitives in JS. If you don't have access to blocking data-handoff between threads, you can choose between callback hell or async/await.

Please take a look at the video I posted earlier.

Re:The Rust I wanted had no future by singingboyo on Monday June 05, 2023 @06:27PM (#36204974)
It's not an artificial distinction at all, but it is subtle.

2, 4, even 256 sockets exchanging data can be worked with concurrently on a single thread and gain performance vs blocking and waiting for the first socket to finish. There's no parallelism, since they're never actively reading/writing at the same time, but they're concurrent because they exist and operate in overlapping timeframes.

Running two independent algorithms could mimic this - run part of algo A, then part of B, then A, etc. It's not useful for performance, though. To be useful you require parallelism - you have to have the algorithms executing at the same time, using multiple threads.

On async itself - it's not perfect, but honestly, there are contexts where it makes a lot of sense. I work on a lot of non-blocking C code - the entire programs are basically epoll and timer driven. As a result, the high level coordination is just callback hell. Async is very nice in comparison.

Yes, you can spin up threads to do everything with blocking, but non-blocking I/O came around specifically because the threads add overhead and kind of suck. It's worth noting that having threads can also infect the codebase. You either have to carefully manage mutexes, or you only communicate with channels and have to worry about keeping things updated and in sync. Sometimes this works great with minimal communication between threads. However, if you have one socket per thread and the sockets are all triggering actions that mess with the same data... it's not so great.

Re:The Rust I wanted had no future by fzeindl on Tuesday June 06, 2023 @01:44PM (#36216463)
They should have fixed the overhead of Threads instead of changing the languages. Fibers/virtual threads eliminate this overhead, Java can run millions of them.
Re:The Rust I wanted had no future by magicalhippo on Monday June 05, 2023 @06:46PM (#36205229)
https://towardsdatascience.com/concurrency-and-parallelism-w...

Besides IO Completion Ports for async IO has been the fastest way to do heavy IO on Windows, and it was introduced years before JavaScript even was a thing.

And that's just one example. So hardly think JavaScript was the main driver there.

Re:The Rust I wanted had no future by unscaled on Monday June 05, 2023 @02:05PM (#36200393)
I'm pretty sure the designers of Rust knows about CSP. Rust has Channels in the standard library after all: https://doc.rust-lang.org/rust-by-example/std_misc/channels....

Rust Channel have the nice additional that they don't panic, and don't have all the weird Go Channel Axioms[1] that you have to wrap your head around to get things to work correctly[2].

For what it's worth, I don't think Java Virtual Threads (a.k.a. project loom) has implemented an explicit concept of channels, although I guess BlockingQueue could work as a channel.

There is a big issue with bringing CSP to languages like Java 21 or Go, that use so-called "colorless concurrency" or "stackful coroutines". CSP losses a lot of its modeling power when you combin shared mutable state and pre-emptive (or invisible) parallelism. And this is exactly what both of these languages do.

"Colored" abstractions like async/await are more cumbersome (especially in Rust), but they let you know exactly at which points of execution your stackless coroutine may suspend, and you can handle synchronization accordingly.

Those functional concepts are cumbersome, but threads ("green", "kernel", "virtual", "goroutines" or otherwise) are not the panacea their proponents claim them to be. I had to debug way too many data races in my life to consider them perfect. Rust still has a lot to improve on its async front (no async traits, async closures or async iterators and the async runtime confusion), it can at least promise me that I won't get any data races.

[1] https://dave.cheney.net/2014/03/19/channel-axioms [2] https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-s...

Re:The Rust I wanted had no future by kaba0 on Tuesday June 06, 2023 @03:53AM (#36209592)
> it can at least promise me that I won't get any data races.

But not any other kind of race condition.

Also, data races are “safe” in java. Plus you can rely on immutable data for those parts. Parallelism will always be hard, there is no model for shared mutability that would be easy.

Re:The Rust I wanted had no future by divan on Monday June 05, 2023 @11:18AM (#36197452)
It's crazy that the programming community even accepted the concept of async/await as a sane one.

Being sync or async is essentially a property of the attention of the caller, not of the action itself. Is "eating a donut" a sync or async action? If I'm focusing all my attention on it, essentially putting all tasks aside (after) - then it's a synchronous action. If I'm reading a book/watching a video/walking/etc, while eating – it's an async action.

How does "func EatADonut() async {}" aka "eating a donut is an inherently async action" even make sense to people?

Re:The Rust I wanted had no future by paulddraper on Monday June 05, 2023 @12:46PM (#36199073)
IDK about donuts.

But consider a network request. The vast majority of the time is not spent in the CPU.

Right?

Re:The Rust I wanted had no future by divan on Monday June 05, 2023 @01:55PM (#36200221)
Doesn't matter, donut was just an abstraction.

s/eat a donut/make a network request/

Is "network request" a synchronous or asynchronous activity? It depends whether your code blocks and wait's until response (or timeout) or continues executing and handling it when it comes. It's property of the "attention" of the caller, not of activity itself.

Re:The Rust I wanted had no future by joshuamorton on Monday June 05, 2023 @03:10PM (#36201728)
But "can be gracefully executed asynchronously" is a property of an action.

Something that pegs the CPU to 100% because it's doing intense processing isn't a good candidate for async. Similarly, some code can cause issues when written in a non-streaming fashion. Take the following example (in python):

    x = [x for x in range(1_000_000_000)]
    y = (x for x in range(1_000_000_000))
If you spawn a bunch of async workers doing the first one concurrently, you'll OOM your system. If you spawn a bunch of async workers doing the second one concurrently, your system will be fine.

In other words, "async" is a label on a box of donuts that implies (though doesn't ensure, people can of course still do bad things) that the donut won't explode if you look away from it.

Re:The Rust I wanted had no future by divan on Monday June 05, 2023 @04:10PM (#36202852)
Hmm, is it a post-factum rationalization or it's the original logic behind async/await? Let's mark "heavy" functions with a label, so user has to call them differently not to overload the system?

Even if this is an original logic, why language is deciding for me what is considered heavy or not? What if I'm fully aware that I'm doing heavy processing and I want it to be happening in the background. What if I'm writing HFT software and every call is heavy for me. Language is not a right level of abstraction of marking "heaviness" of the code.

It really just doesn't make sense. Why stop there and not start marking functions with how many times per second they can be called? Like you can call "light" function 100 trillion times per second and OOM the system, so let's mark it "func light() async 2_times_per_second {}". It only can be called from functions that have lower per second label.

Plus, if Python cares so much about not OOM-ing the system I would start by not requiring 32-bit int to occupy 28 bytes in the first place.

Re:The Rust I wanted had no future by johnnyanmac on Saturday June 10, 2023 @07:55AM (#36269734)
>is it a post-factum rationalization or it's the original logic behind async/await?

Well, I'm not that deeply versed into any language's history, but I imagine any 15+ year old language had a point where they needed asyncrhonous functionality but had to write around legacy code. I don't think Javascript would have been written the way it was in the 90's if asynchronous operations were a mandatory priority.

So it's probably a lot more "this is how we hack this in without creating Python 3.0". Relatively elegant to make it an optional part of the language that you explicitly choose to delve into, instead of a core feature that would have broken thousands of sites behind the scenes if you made it "right".

>why language is deciding for me what is considered heavy or not?

because we decided decades ago that we didn't like using fork(), nor creating/destroying processes ourselves. problems that would inevitably need multiple solutions when supporting multiple platforms because these aren't language features so much as OS calls made in a language that you may or may not be writing in.

Remember at the end of the day that languages are abstractions on top of an OS. Any interesting ideas for problems involving more than a single process space that your OS makes for you is bound by what the OS's API lets you do. It's arbituary but also based on historical problems to wrangle.

So we haven't had enough problems where we need to bound a function by how much it is called. We have for how to interact with all the above OS/language problems.

Re:The Rust I wanted had no future by wizofaus on Monday June 05, 2023 @04:26PM (#36203067)
It's only functions that "block and wait" that it makes sense to be async - the point being that blocking and waiting doesn't use up CPU cycles, so you might as well free the thread up to do other stuff. If a function issues a socket write, then continues utilising the CPU while also periodically checking if data's available to read, it's effectively doing manually what async does for you, though I'm not sure there are too many real life examples of such functions. But pre-async it was certainly common enough for block-and-wait functions to tie up a thread and hence execution of programs with limited multi-threading (even today, GUIs often require all events and updates to be processed on the primary thread).
Re:The Rust I wanted had no future by kmac_ on Monday June 05, 2023 @01:38PM (#36199926)
How about waiting until all or part of donut pack is eaten by multiple eaters? Jokes aside, async/await comes from easier handling of a callback code and automatic function splitting. Writing asynchronous donut eating in loops or with yielding of partial results is very easy to swallow with such syntactic sugar.
Re:The Rust I wanted had no future by divan on Monday June 05, 2023 @02:00PM (#36200310)
Doesn't matter. You either wait until donut is eaten by others or you don't. Caller is deciding if it's "synchronous" (blocking) action or not.

The question is how you translate mental models from real world to the code, and async/await fails here spectacularly. It's just weirdly unnatural thinking about sequencial processes. It requires tremendous amount of cognitive gymnastics just to reason about simple things.

Re:The Rust I wanted had no future by mplanchard on Monday June 05, 2023 @07:29PM (#36205812)
I’m sorry but isn’t this exactly how async/await works from the perspective of the caller? If you want to wait until the donut is eaten you `.await`. If you don’t, you do other stuff and then join the future later. The fact that the executor can get other stuff done while you’re awaiting the donut consumption is largely invisible from the caller’s perspective.
Re:The Rust I wanted had no future by divan on Tuesday June 06, 2023 @05:30AM (#36210261)
That's only if you marked function with "async". Developer who writes code for "eating a donut" supposedly has better knowledge of how I'm supposed to eat it - synchonously or asynchronously. So if they didn't mark function as "async" I can't decide how I should eat it, it's decided for me.
Re:The Rust I wanted had no future by kmac_ on Tuesday June 06, 2023 @04:22PM (#36218609)
You can eat it in the same thread. Some frameworks do that implicitly (even without await) . You can also control if it's consumed in particular thread or thread pool and decide which thread should continue. More, you can chain and/or parallelize it with other async and not-async functions without any effort. Additionally, you can handle errors and exceptions same as it's async and not-async function. Every good async/await framework offers that out of the box. More, some frameworks abstract executors and you can run the function remotely or even persist it with context. Of course you can achieve all that without async/await, but it makes that extremely easy.
Re:The Rust I wanted had no future by pmoleri on Monday June 05, 2023 @02:13PM (#36200537)
> How does "func EatADonut() async {}" aka "eating a donut is an inherently async action" even make sense to people?

Of course it does, read it as "beware, something blocking down the road".

If you can EatADonout without blocking, please do, but want it or not that's a different implementation, one that doesn't block and the signature it's telling you so.

We're so used to sync and having hidden blocking operations. I wonder if in an alternate universe the first languages considered the blocking/async nature of operations and then some newer languages considered hiding this information into seemingly sync functions would produce similar but opposite outrage against it.

Re:The Rust I wanted had no future by divan on Monday June 05, 2023 @03:29PM (#36202210)
> want it or not that's a different implementation,

Implementation is the same. In both cases it's the same set of CPU instructions, but async/await languages create artifical division, forcing developer to think othervise.

So, let me explian my reasoning. Code starts with a developer's mental model of a problem and behavior of the system and then translating it into the code. The more straightforward this translation, the more readable the code. Code is a second degree map of problem domain so to speak ("real" world -> mental model -> code).

Like if you want to add two values, the simplest form of code would be "add(2,2)" which is pretty straightforward. If the code forces you to do some mental gymnastics (i.e. "2 2 addOnlyEvenNumbers") - that's less straightforward, less clear and less readable.

In the same vein, if you want to execute some function ("EatADonut" or "MakeHTTPCall") – you may care or not care about blocking and waiting for results. But it's your call. So it makes sense to give you two options to run this function. Go has simplest possible solution – "eatADonut()" vs "go eatADonut()". It doesn't matter what is a "default" here – it could be "eatADonut()" (go to background) vs "sync eatADount()". What matters that "eating a donut" is just a set of instructions to the CPU, and it's up to caller to decide how you want to execute it in terms of concurrency.

Now, "async/await" approach turned this ownership of "synchronicity" around. Now function is deciding how it should be called. Mental model of "actions" now needs to be translated into "actions being async or sync for the purpose of fitting into this language concept". Which is cognitively expensive for no added benefit.

Sure you can rationalize it, and get used to it as to any other absurd design, but it still adds unneeded complexity to the code, makes it less readable and less clear.

Re:The Rust I wanted had no future by johnnyanmac on Saturday June 10, 2023 @07:40AM (#36269656)
* * *
Re:The Rust I wanted had no future by bourneavent on Monday June 05, 2023 @02:19PM (#36200631)
>How does "func EatADonut() async {}" aka "eating a donut is an inherently async action" even make sense to people?

You have to think of it from a higher level.

Nobody knows if eatdonut is strictly async but everyone knows all IO calls are async. So something like socket.send would be async, this is obvious.

Then anything that calls socket.send would then in turn become async itself. The async sort of spreads around like a virus. Any function that uses other async functions gets polluted with the async marker.

So in this sense, you should think of it like this. If eatADonut is async it means it touches IO. It sends data somewhere else to some other thing. It also means it's not unit testable.

In a nut shell This is the intuition you should have about async:

   Async Tells You about the properties of eatADonut. eatADonut doesn't tell you anything about whether or not it's async.
This is largely identical to how the IO monad works in Haskell. Unless you want all your logic polluted with the IO monadic wrapper or the async marker you have to learn to segregate your functions into async functions and non-async functions.

Under this intuition, EatADonut, by good design principles should NOT be async. This is what it should be:

    fn eatAdonut() -> ResultOfEatADonut
    async fn sendDataToIO(data: Data) -> ()

    async parentCaller() -> () {
       let data = eatAdonut();
       sendDataToIO(data.serialize()).await;
       ()
    }
The async marker forces you to organize your code in this way, You have to do this unless you want all your functions to be polluted by the async marker. This is the proper way to organize code Anyway, as it allows your code to be unit testable. Async calls are, in general, not unit testable because the functions are not pure. So if you look at what I did above, I segregated eatAdonut Away from IO and made part of the logic of the overall code testable via unit tests.

IO calls should be very general and small functions while your other functions should be pure and unit testable. This is the overall intuition you should have about async.

Believe it or not, this specific aspect of async is actually an improvement over golang style concurrency. Not an overall improvement, it's still debatable which style is better, but I'm saying the async marker is actually a feature that other styles of implementing concurrency don't offer.

Basically, async encodes the concept of IO and function purity into the type system. It allows the type checker and You to tell which functions touch IO and which functions are pure and unit testable.

People think unit testability is some kind of marginal benefit because you can cover testing with integration tests which are basically tests that happen across IO. It's more than that. The ultimate dream of programming something as if it's a bunch of modular lego blocks that you can elegantly place together is most fully realized with pure functions. Async enables more of this style of modularity in Rust (but not without some cost which is why it's debatable whether or not it's better than go style concurrency).

Re:The Rust I wanted had no future by kaba0 on Tuesday June 06, 2023 @03:50AM (#36209566)
> but everyone knows all IO calls are async

Do you mean on a hardware level? Because otherwise, hell no.

If I’m writing a binary file parser that buffers some data and processes it, I sure as hell want to wait for IO, there is no other meaningful way forward in most cases. It is also not a small isolated part, but a tight loop bouncing between CPU and IO.

Async is a function of the caller.

Re:The Rust I wanted had no future by bourneavent on Tuesday June 06, 2023 @04:53AM (#36210019)
>Do you mean on a hardware level? Because otherwise, hell no.

I mean in the context of async await. The smallest primitive that is properly async is an IO function.

Re:The Rust I wanted had no future by bmacho on Monday June 05, 2023 @03:47PM (#36202486)
This was my first (and obviously wrong) mental model of how async works.

There are functions, you can call them sync or async if they handle IO or UI or they will get a necessary data later.

I still don't understand why I can't fetch a URL from top level javascript. Also I don't understand why zig async and await passes the control flow that seemingly total arbitrary way. The naive approach (put async calls in a queue, and periodically check if they are completed or can be executed) seems fast, deterministic, and good enough in every way?

Okay, maybe zig needs the speed, and can't just stop the execution of the sync code time to time to do something else, but why javascript? Maybe javascript async is build upon regular promises and regular objects, instead as a proper language element with proper support in the javascript engines? I don't know. Anyway async as is used with colors is totally against the picture that the words "async" and "await" suggest.

Re:The Rust I wanted had no future by rovolo on Monday June 05, 2023 @08:11PM (#36206286)
> I still don't understand why I can't fetch a URL from top level javascript.

> Maybe javascript async is build upon regular promises and regular objects, instead as a proper language element with proper support in the javascript engines?

Yes, they are promises.

> The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

> The naive approach (put async calls in a queue, and periodically check if they are completed or can be executed) seems fast, deterministic, and good enough in every way? Okay, maybe zig needs the speed, and can't just stop the execution of the sync code time to time to do something else, but why javascript?

You're describing preemptive multitasking. You can create separate execution threads pretty easily in most languages (even super old C code), but that's not why Javascript and Zig have the 'async' keyword. The difficult part isn't the asynchronous execution; the hard part is handling the result from asynchronous code (i.e. the 'await' part). Promises are a simple mental model for organizing how pieces of code depend on the results of other pieces of code. The C# documentation does a good job explaining this:

https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous...

Note: From Zig's documentation it sounds like 'async' is cooperative multitasking. This is single-threaded execution. It is "concurrent" code, but it isn't executing in "parallel".

Concurrency is difficult because it is explicitly resource management. You don't need concurrency for calculating the correct answer, you only need it for managing time.

Re:The Rust I wanted had no future by lmm on Monday June 05, 2023 @07:46PM (#36206014)
Does blocking I/O or not is very much a property of the function itself, as is "may finish up running on a different thread from the one it started on" (or, if you prefer, "needs to be run under a runtime that provides that capability").

Sending a letter and getting a reply is an inherently async action; you can stare at the mailbox all day but you probably don't want to. Waiting in line at the bank is inherently sync; if you try to do something else and then come back they'll make you start over again.

Re:The Rust I wanted had no future by divan on Tuesday June 06, 2023 @09:20AM (#36212256)
> you can stare at the mailbox all day but you probably don't want to

Yet, it's up to me to decide, not to the mailbox.

Re:The Rust I wanted had no future by lmm on Tuesday June 06, 2023 @05:52PM (#36219554)
You can always call blocking await on an async function. It's just usually a bad idea.
Re:The Rust I wanted had no future by Too on Tuesday June 06, 2023 @02:55AM (#36209135)
You can see the async keyword on a function definition as a hint to the caller, that the action will be IO bound rather than CPU bound.

Caller can use this information to invoke 10000 IO bound tasks in parallel, whereas cpu bound tasks are limited to number of cores and may want to be throttled. In your analogy, I can eat a donut while simultaneously listening to music, but I can not eat a donut while simultaneously eating fish soup.

Now where it get complicated is when a function does both io wait and heavy cpu processing. Now neither color of the function match.

Forcing this upon the caller to invoke such functions in special ways is the strange part. Taken to the absurd you ought to have special syntax for every resource constraint in the computer, memory bound, disk bound, wifi bound, and so on. It would be better if the runtime could figure this out by itself and parallelize accordingly.

The single threaded aspect of async functions does have certain other benefits, like not having to care as much about muxes when synchronizing state.

Re:The Rust I wanted had no future by kaba0 on Tuesday June 06, 2023 @03:38AM (#36209476)
There are so many nuances to function execution’s resource usages that I don’t see a point in putting boxes on that. The important part and the more intuitive human model for execution is from the POV of a single, calling thread. Whether it semantically makes sense to wait for the result of this call, or it doesn’t. If you need the result there is no going around that, you have to wait on that — it’s this function’s caller that has to decide what to do now.

Other possible semantics would be to start multiple tasks and wait for them all — this is where structured concurrency comes in, giving a good analogue with gotos.

Async-await is fundamentally an optimization, not about semantics only. Languages without runtimes simply can’t reason about it without language support, but managed ones could do so as every IO call goes through it — scheduling multiple tasks to a single core is now possible.

Re:The Rust I wanted had no future by Zababa on Tuesday June 06, 2023 @06:52AM (#36210806)
Async/await, at least in JS, is here to make asynchronous code look like synchronous code, as in you can say "let toto = await asynchronousFunction(); doSomething(toto)".

Without it when you want to do asynchronous stuff you either block on it (not good since JS is single-threaded), eg "let toto = asynchronousFunction(); doSomething(toto)" and nothing can be done during the time asynchronousFunction is waiting on IO or something (in a browser environment that means part of your website stops responding to user input); or pass as an argument to the asynchronous function a function that will be executed by the asynchronous function once it "wakes up" (usually called a "callback"), and while it's waiting, the JS runtime can execute other stuff, eg "asynchronousFunction((toto => doSomething(toto)))".

Callbacks were I think from the start in JS. After a while promises were introduced, which compared to callbacks avoids nesting, and probably have some other advantages that I don't know about. Still, they are method chaining and not "regular code". For example regular try/catch won't work as usual, you have to use .catch(). Even later async/await got introduced, which allow you to write asynchronous code as if it was regular code, with a few exceptions (for example you can only use await in an async function, top-level await took a while to land on Node, stuff like that).

I don't think using real world human actions helps with understanding any of that. Async functions make sense in JS because again, JS is single threaded and if you do a blocking call (like asking the kernel to read a file, or making an HTTP request and waiting for the response), the event loop is blocked. From Node.js documentation's on the synchronous function in the fs module:

> The synchronous APIs perform all operations synchronously, blocking the event loop until the operation completes or fails.

Re:The Rust I wanted had no future by mananaysiempre on Monday June 05, 2023 @01:46PM (#36200087)
> [Async/await] feels so complicated and hacky, a mediocre very high level concept that someone managed to implement as a zero-cost abstraction.

I feel like I need to point out that Donald “Structured programming with GO TO statements” Knuth included an example of coroutines in the first volume of The art of computer programming, the first edition, dated 1968. In assembly language for an accumulator machine. With a box of scraps!^W^W^W^W^WThat is to say, that C and most other languages have made coroutines awkward and thus virtually unused in the past three decades or so does not mean they are particularly novel or high-level.

Granted, Knuth used coroutines in a simulation and not for I/O, so he did not need that much of a scheduler, but still.

Re:The Rust I wanted had no future by throwawaymaths on Monday June 05, 2023 @02:39PM (#36200995)
> a zero-cost abstraction

It's just an abstraction, it's not zero-(runtime)-cost. It might be the "lowest possible cost", still nonzero.

Re:The Rust I wanted had no future by mplanchard on Monday June 05, 2023 @07:44PM (#36205984)
A zero cost abstraction is just an abstraction you couldn’t write any better yourself, not one that is actually free. If you’re lookint for an item in a hashmap, you still have to pay the cost of looking up the key. The promise of zero-cost abstractions is that you’re paying the lowest possible cost to do that, with a nice interface that you didn’t have to write from scratch.
Re:The Rust I wanted had no future by throwawaymaths on Monday June 05, 2023 @08:05PM (#36206208)
Agreed, that's why I said might. Whether rust async is the best possible abstraction probably depends on the event loop, and your code. I could be wrong but I think if you write a trivial async function it will still have spinach for the async even if there are no yield points and it's effectively a non async function
Re:The Rust I wanted had no future by jaxrtech on Monday June 05, 2023 @02:54PM (#36201294)
If Rust was a higher-level language, then I'd say yes, just automatically handle running Futures in parallel, join them when you actually need to resolve the data, and pretend that they look like synchronous functions in the 80% of cases. Though things like `select!` wouldn't make any sense mixing the two.

I continue to find the "function coloring" argument misses the point unless you're arguing from a developer experience perspective. Why should two fundamentally different things look and function the same? Want this the ultimate pitfall of early RPC implementations where everything looks synchronous?

In Rust, a lot of the friction is due to how async functions/futures fundamentally differ in model of execution and how that interplays with the rest of the language. Other languages get to hand-wave a lot of these problems away with a GC. (It certainly could be less of a pain nonetheless.)

- Futures don't execute until polled, can partially execute if something isn't ready (and would block), can partially execute and be arbitrary cancelled and dropped. There is no equivalent in sync functions unless you make them co-routines.

- Since you can "pause" futures if something would block, you need a place to store the function's state for when it's resumed. Now you must be consider if the async function's state is `Send` if the future wants to move to a different thread to continue executing -- which is why you see `pin!` used. Sync functions don't care about this since you always run to completion on the same thread, and the stack is ephemeral.

- Likewise, the `Future` returned by the async function is going to need to encapsulate it's state that it saves. In the general case, this is a compiler generated anonymous struct that changes if any state saved across `.await` changes, hence the opaque `impl Future`. This is why you see `BoxedFuture` a lot to abstract this away at the expense of an allocation. Ideally, the new associated types with lifetimes can avoid this with traits.

So if all functions were co-routines (i.e. functions that can be resumed and re-entered) they would all have the same "color". But all you really did was "lift" all sync functions to be "async" functions with no internal await points.

(IMHO, if the C# team back in the day decided to implement full blown co-routines into the language instead of just `async/await` as a compiler trick, I think many other projects would have followed suit with a more general co-routine solution instead of treating `async/await` as this special thing which is just a specific compiler generated implementation of co-routines.)

Re:The Rust I wanted had no future by bsder on Monday June 05, 2023 @05:38PM (#36204156)
> That said, I still hate async with a passion, it makes the language more complex and not very elegant (i.e. function coloring).

async in Rust is a popularity issue even more than a technical issue.

A whopping amount of people who are coming to Rust are doing so because they want a good ecosystem for implementing network service servers. Rust/cargo/crates hits the sweet spot for them.

I am with you that I loathe async/await in Rust. However, I also have to acknowledge that without async/await, Rust is a vastly less popular language.

All the other uses of systems programming are simply dwarfed by number of people building network services. That's just the sad reality.