Live data from Hacker News

Local async executors and why they should be the default

maciej.codes

171–180 of 327 posts

Re: Local async executors and why they should be the default

#171
post #165
post #158

Earlier quoted context omitted.

Rust sweetspot is really for use cases where any kind of automatic memory management is forbidden, either due to real use case requirements (high integrity computing, kernel drivers,...), or due to existing domain culture that frowns upon any other kind of alternatives. For everything else, there are more productive alternatives, even Go, which after generics is kind of ok.

Maybe for prototypes or small scripts but disagree otherwise. The promise of automatic memory management is that you don't have to think about memory. But the second you don't think about memory you are doomed to write bad code anyway. Maybe because of performance but most probably due to architecture. To have a language that forces you to think about memory is not a curse, it is a blessing.

Well, I also disagree.

Just because a language has some form of automatic memory management, that doesn't mean it doesn't provide the same mechanisms of a language like C for low-level programming.

Plenty of options, D, Go, Swift, C#, Nim, Eiffel, Common Lisp, Haskell, OCaml,....

Dead options that unfortunely didn't manage to win market adoption, Oberon, Oberon-2, Active Oberon, Component Pascal, Modula-2+, Modula-3, Cedar,

Speaking of Cedar, when will Rust managed to have a full workstation OS?

"Eric Bier Demonstrates Cedar"

https://www.youtube.com/watch?v=z_dt7NG38V4

Redox still has a bit to catch with what a full graphical workstation OS was capable of in 1980's hardware, using a systems programming language with automatic memory management.

Naturally, there are other examples out from Xerox PARC, Genera and Texas Instruments on that regard.

Re: Local async executors and why they should be the default

#172

I'm still not sure what async (cooperative multitasking) gives over green threads (preemptive userspace multitasking)

Lower overhead. It's the same reason you would need to avoid a GC. It may not be for everyone, but that isn't a rust goal necessarily.

Re: Local async executors and why they should be the default

#173
post #171
post #165

Earlier quoted context omitted.

Maybe for prototypes or small scripts but disagree otherwise. The promise of automatic memory management is that you don't have to think about memory. But the second you don't think about memory you are doomed to write bad code anyway. Maybe because of performance but most probably due to architecture. To have a language that forces you to think about memory is not a curse, it is a blessing.

Well, I also disagree. Just because a language has some form of automatic memory management, that doesn't mean it doesn't provide the same mechanisms of a language like C for low-level programming. Plenty of options, D, Go, Swift, C#, Nim, Eiffel, Common Lisp, Haskell, OCaml,.... Dead options that unfortunely didn't manage to win market adoption, Oberon, Oberon-2, Active Oberon, Component Pascal, Modula-2+, Modula-3,…

I wasn't talking about low level programming?

High level programming also benefits from manual memory management.

Re: Local async executors and why they should be the default

#174
When I was between jobs, I decided to learn rust by writing some async code. I really got stung and spent days doing things that would take minutes in C# or Node.

Part of the problem is that, in Rust, stack memory is easier to use than heap memory. When writing traditional threaded code, this isn't much of an issue because naturally most of our code is working with values on the stack.

BUT: When we look more closely in how async works in C# or Javascript, the compiler, under the hood, breaks up an async method into multiple methods and puts values that appear to be on the stack onto the heap. (Of course, I'm over simplifying.) It just works, and it just works well.

But, in Rust, making something async implicitly moves what appears to be on the stack onto the heap. It can quickly become hard to reason about.

I wish I knew about techniques that this article describes. Maybe it would make my code easier? In my current hobby projects, I'm doing traditional blocking IO because it's not "worth it" to write async. (In comparison, in Node, async code helps avoid nesting callbacks within callbacks, and in C#, doing IO in async is a best practice.)

Re: Local async executors and why they should be the default

#175
post #173
post #171

Earlier quoted context omitted.

Well, I also disagree. Just because a language has some form of automatic memory management, that doesn't mean it doesn't provide the same mechanisms of a language like C for low-level programming. Plenty of options, D, Go, Swift, C#, Nim, Eiffel, Common Lisp, Haskell, OCaml,.... Dead options that unfortunely didn't manage to win market adoption, Oberon, Oberon-2, Active Oberon, Component Pascal, Modula-2+, Modula-3,…

I wasn't talking about low level programming? High level programming also benefits from manual memory management.

Great that D, Go, Swift, C#, Nim, Eiffel, Common Lisp, Haskell, OCaml,.... also offer ways of doing it.

Re: Local async executors and why they should be the default

#176
post #63

Earlier quoted context omitted.

> Last but not least - often making futures Send requires you to box your futures. Send and lifetimes just does not play well together. Do something like this: You should ~never need to box your futures to make them Send. You do sometimes need to box them to make them Unpin, but most of the time this is a tradeoff where it's possible to find a way to avoid moving them instead. It seems like these screeds (both the OP…

Look at the extremely popular async_trait macro crate: https://crates.io/crates/async-trait . What it does under the hood is to box everything. See https://github.com/dtolnay/async-trait#explanation You would think that you should be able to desugar async traits to something that does not need to box. But this is not possible with stable rust. There is an effort to make this possible. See for example this discussion…

Yes, it currently needs to box. But again, Sync is just a symptom of the real issues here (async fns have unpronouncable return types, opaque return types (impl trait) isn't supported in traits, and we don't have a good syntax for expressing constraints on opaque return types). Getting rid of Sync wouldn't fix any of those issues.

Re: Local async executors and why they should be the default

#177
post #175
post #173

Earlier quoted context omitted.

I wasn't talking about low level programming? High level programming also benefits from manual memory management.

Great that D, Go, Swift, C#, Nim, Eiffel, Common Lisp, Haskell, OCaml,.... also offer ways of doing it.

You don't seem to care but you are missing the point.

Re: Local async executors and why they should be the default

#178

I'm still not sure what async (cooperative multitasking) gives over green threads (preemptive userspace multitasking)

Lower overhead. It's the same reason you would need to avoid a GC. It may not be for everyone, but that isn't a rust goal necessarily.

I'm aware that Rust's threading has higher overhead because they're system threads, but what about green/user threads? Is there intrinsic overhead to userspace threads that async doesn't have? I think every "task"/thread needs it's own callstack space, and you're paying scheduling overhead no matter what. Is there literature on the topic?

Re: Local async executors and why they should be the default

#179
post #165
post #158

Earlier quoted context omitted.

Rust sweetspot is really for use cases where any kind of automatic memory management is forbidden, either due to real use case requirements (high integrity computing, kernel drivers,...), or due to existing domain culture that frowns upon any other kind of alternatives. For everything else, there are more productive alternatives, even Go, which after generics is kind of ok.

Maybe for prototypes or small scripts but disagree otherwise. The promise of automatic memory management is that you don't have to think about memory. But the second you don't think about memory you are doomed to write bad code anyway. Maybe because of performance but most probably due to architecture. To have a language that forces you to think about memory is not a curse, it is a blessing.

> But the second you don't think about memory you are doomed to write bad code anyway.

Yeah but the failure modes are different.

"Doomed" in C++ means now I've got worms all over my network. Pretty much a fully negative outcome.

"Doomed" in Rust means I can't change it anymore until I relearn and refactor everything. If it's a pacemaker or rocket engine, that's probably ideal. But if it's not...

"Doomed" in Go or Java means I gotta cough up another few GB of RAM and maybe kick over the service every month. For some random middleware, that's fine.

Re: Local async executors and why they should be the default

#180

Earlier quoted context omitted.

The supervisory system is an extra, you don't technically need it but it can help make your application bullet proof and I would definitely recommend if you go the Erlang route to use it to your advantage. Every other language and/or runtime will need something similar anyway, but there is a good chance that it won't be nearly as elegant (as as solid) as the way Erlang does this. Agreed that actors are not something…

> The supervisory system is an extra, you don't technically need it but it can help make your application bullet proof and I would definitely recommend if you go the Erlang route to use it to your advantage. An actor system without supervisors is throwing away a lot. How do you handle an actor that crashes? The thing is, again, Actors are very low level - they're a foundational model. You end up having to build proto…

I am responding to the very last point about dropping a future and the resource usage.

Let's assume a future that implements reading from a network socket. And the file descriptor (fd) was already passed to epoll/select (I am ignoring io_uring).

After dropping the future, the socket is is still being considered by the kernel for reading. Eventually epoll will return, and the IO runtime (like tokio) will notice the stray fd and discard it. And can you even close the fd before it is out of epoll?

So until the fd is back to user land, it appears very similar to leaking resources. Even if only for a short time while the IO loop is completing an iteration.

Post reply on HN