Live data from Hacker News

Maybe Rust isn’t a good tool for massively concurrent, userspace software

bitbashing.io

61–70 of 624 posts

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#61

Earlier quoted context omitted.

If someone is foolish enough to implement a websocket library that insists on performing I/O by calling the methods on the language's generic reader and writer interfaces (rather than just operating on buffers the user passes in or whatever), why should they need to implement it a second time to add async? There are plenty of languages in which they do not need to do this, like Python or Lua.

How does that relate to async/await? This is an arbitrary implementation choice. Generic reader/writer in Rust wouldn't matter since it will get optimized away, only leaving cooperative multitasking code if the buffer were to be filled by another thread/producer/source.

If you write a library that implements websockets on top of the Read trait or on top of sockets, is it *also* an async websocket library, with no additional code and without containing any instances of the async keyword? In many languages the answer is yes.

I guess I should specify that this is true even in a single-threaded context and even without any additional buffers or whatever.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#62
Anyone know why there isn't a single type/interface that allows for consumers to supply any of Arc, Rc etc boxed values?

I haven't investigated it deeply, but I was developing something in Rust, and whether something needs to be threadsafe or not is entirely on the consumer's use case... bad separation of concerns for the provider of a generic interface to have to specify the specific type of boxed value. 100% fine if the behavior in this case is to pre-allocate the max possible boxed type memory requirement.

This is the only thing I was really frustrated with in Rust

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#63
> You might say this isn’t a fair comparison—after all, those languages hide the difference between blocking and non-blocking code behind fat runtimes, and lifetimes are handwaved with garbage collection. But that’s exactly the point! These are pure wins when we’re doing this sort of programming.

Until all the work you're trying to push is generating so many allocations that your GC goes to shit once every two minutes trying to clean up the mess you made. (https://discord.com/blog/why-discord-is-switching-from-go-to...)

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#64

Async Rust is especially problematic in the enterprise world where large software is built out of micro-services connected through RPC. Typically, if you want to build something with Rust, it'll have to use async, at least because gRPC and the like are implemented that way. So the vanilla (and excellent, IMO) Rust language doesn't exist there. Everything is async from the get-go.

> Async Rust is especially problematic in the enterprise world where large software is built out of micro-services connected through RPC.

A weird way to use Rust since you can do a lot of messaging within the process, and use the computing power much more efficiently.

RPC is essentially messaging and message-passing. Message-passing is a way to avoid mutable shared state - this is the model with which Go became successful.

RPC surely has its use but message passing is another, and very often inferior, solution to the problem set where Rust has excellent own solutions for.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#65
post #2

> Used pervasively, Arc gives you the world’s worst garbage collector. Like a GC, the lifetime of objects and the resources they represent (memory, files, sockets) is unknowable. But you take this loss without the wins you’d get from an actual GC! The lifetime of an Arc isn’t unknowable, it’s determined by where and how you hold it. I think maybe the disconnect in this article is that the author is coming at Rust and…

Reference counting is a type of GC [0]. Just not a very good one in many cases. I think it's a fair assumption to say that the author is aware of what Arcs are and how they work. I believe their point is more so that because of how async works in Rust, users have to reach for Arc over normal RAII far more often than in sync code. So at a certain point, if you have a program where 90% of objects are refcounted, you mi…

> I think it's a fair assumption to say that the author is aware of what Arcs are and how they work.

Fair, but when reading an article like this I have to refer to what's written, not what we think the author knew but didn't write.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#66
post #44
post #32

Earlier quoted context omitted.

Exactly. People are too afraid of using threads these days for some perceived cargo-cult scalability reasons. My rule of thumb is just to use threads if the total number of threads per process won't exceed 1000. (This is assuming you are already switching to communicating using channels or similar abstraction.)

99% of the use cases that ought to use async are server-side web services. If you're not writing one of those, you almost certainly don't need async.

Or desktop programs. Many GUI frameworks have a main thread that updates the layout (among other things) and various background ones.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#67
post #44
post #32

Earlier quoted context omitted.

Exactly. People are too afraid of using threads these days for some perceived cargo-cult scalability reasons. My rule of thumb is just to use threads if the total number of threads per process won't exceed 1000. (This is assuming you are already switching to communicating using channels or similar abstraction.)

99% of the use cases that ought to use async are server-side web services. If you're not writing one of those, you almost certainly don't need async.

Note that if you "just" write responses to queries without yielding execution, you don't need async, you just write Sync handlers to an async framework. (Hitting dB requests in a synchronous way is not good for your perf though, you better have a mostly read / well cached problem)

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#68
Yes, async is effectively a much harder version of Rust, and it's regrettable how it's been shoved down the throats of everyone, while only 1% of projects using it really need it. Hover, async is also amazing in these 1% of cases when it's useful.

If you have a service that handles massive amounts of network calls at the core (think linkerd, nginx, etc.), or you want to have a massive amount of lightweight tasks in your game, or working on an embedded software where you want cooperative concurrency, async Rust is an amazing super-power.

Most system/application level things is not going to need async IO. Your REST app is going to be perfectly fine with a threadpool. Even when you do need async, you probably want to use it in a relatively small part of your software (network), while doing most of the things in threads, using channels to pass work around between async/blocking IO parts (aka hybrid model).

Rust community just mindlessly over-did using async literally everywhere, to the point where the blocking IO Rust (the actually better UX one) became a second class citizen in the ecosystem.

Especially visible with web frameworks where there is N well designed async web frameworks (Axum, Wrap, etc.) and if you want a blocking one you get:

  tiny_http, absolute bare bones but very well done
  rouille - more wholesome, on top of tiny_http, but APIs feel very meh comparing to e.g. Axum
  astra - very interesting but immature, and rather barebones

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#69
post #32
post #16

Earlier quoted context omitted.

Hoare Was Right. (But if you're only firing up a few tasks, why not just use threads? To get a nice wrapper around an I/O event loop?)

Exactly. People are too afraid of using threads these days for some perceived cargo-cult scalability reasons. My rule of thumb is just to use threads if the total number of threads per process won't exceed 1000. (This is assuming you are already switching to communicating using channels or similar abstraction.)

The challenge is that async colors functions and many of the popular crates will force you to be async, so it isn't always a choice depending on which crates you need.

Re: Maybe Rust isn’t a good tool for massively concurrent, userspace software

#70
post #11

Earlier quoted context omitted.

> The lifetime of an Arc isn’t unknowable, it’s determined by where and how you hold it. In the same sense that the lifetime of an object in a GC'd system has a lower bound of, "as long as it's referenced", sure. But that's nearly the opposite of what the borrow checker tries to do by statically bounding objects, at compile time. > maybe the disconnect in this article is that the author is coming at Rust and trying t…

Honestly, the biggest stumbling block for rust and async is the notion of memory pinning. Rust will do a lot of invisible memory relocations under the covers. Which can work great in single threaded contexts. However, once you start talking about threading those invisible memory moves are a hazard. The moment shared memory comes into play everything just gets a whole lot harder with the rust async story. Contrast tha…

> Rust will do a lot of invisible memory relocations under the covers.

I don't think it's quite accurate to point to "invisible memory relocations" as the problem that pinning solves. In most cases, memory relocations in Rust are very explicit, by moving an owned value when it has no live references (if it has any references, the borrow checker will stop you), or calling mem::replace() or mem::swap(), or something along those lines.

Instead, the primary purpose of pinning is to mark these explicit relocations as unsafe for certain objects (that are referenced elsewhere by raw pointer), so that external users must promise not to relocate certain objects on pain of causing UB with your interface. In C/C++, or indeed in unsafe Rust, the same idea can be more trivially indicated by a comment such as /* Don't mess with this object until such-and-such other code is done using it! */. All pinning does is to enforce this rule at compile time for all safe code.

Post reply on HN