Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> But rendering in separate threads turned out to be (unsurprisingly) harder than the way I would do it in C++...It was a bit frustrating to figure out how to accomplish this. Googling yielded a few stack overflow posts with similar questions, and were answered by people basically saying use my crate!

Based on some discussion in r/rust (https://www.reddit.com/r/rust/comments/c7t5za/writing_a_smal...) I went ahead and added a Rayon-based answer to that SO question (https://stackoverflow.com/a/56840441/823869). That's been the de facto standard for data parallelism in Rust for the last few years. But the article highlights that discovering the de facto standards is still a challenge for new Rust users -- does anyone know of a well-maintained list of the 10-20 most critical crates that new users should familiarize themselves with after reading The Book? Things like Rayon and lazy_static. The ranked search results at https://crates.io/crates?sort=recent-downloads are almost good enough, but they include a lot of transitive dependencies that new users shouldn't care about. (I.e. `regex` is a very important crate, but `aho-corasick` is usually only downloaded as a dependency of `regex`.)



> But the article highlights that discovering the de facto standards is still a challenge for new Rust users -- does anyone know of a well-maintained list of the 10-20 most critical crates that new users should familiarize themselves with after reading The Book?

I came across this exact thing recently: https://github.com/brson/stdx


That seems old, especially as error-chain is no longer maintained so I wouldn't recommend it.

[0]: https://users.rust-lang.org/t/error-chain-is-no-longer-maint...


I'm new to Rust, so I wasn't aware. Thanks!



I will say, the one example they have there which is sort-of analogous to "render each pixel of this image in parallel" is the "draw a julia set" one [0], and it's a very bad way of convincing a C/C++ programmer that Rust is good at this sort of thing. Even if the "loop over all rows in the main thread, adding to the pool a lambda that loops over each column" is somehow optimized in a good data-parallel way (I doubt it compares favorably performance-wise with "#pragma omp parallel for"), the lambdas then push each finished pixel into a channel along with their coordinates. The main thread then has to literally loop through every pixel and read from the channel for each and every one.

The natural way to do that in C/C++ is to just write the pixel to the bitmap in each thread. There are no race conditions here (everything is embarassingly parallel), just write the resulting pixel to the bitmap and be done with it. The only reason to have that channel with all that overhead (and that final synchronization on the main thread) is to satisfy the borrow checker, which is just silly in this case. It adds a tremendous amount of overhead just to make it idiomatic Rust.

It's true that you can do it the "C++ way" in Rust using unsafe and raw pointers (and there's probably crates that can do the "parallel for" in a way that compares well with OpenMP), but as a graphics programmer who's done a lot of this sort of thing, that piece of code made a very bad first impression of Rust as a high-performance language.

EDIT: also, the description is wrong. It says: "ThreadPool::execute receives each pixel as a separate job". No it doesn't, it receives each scanline as a separate job. It might be better if the pool had each pixel as a separate job, but that's not what the code is doing.

[0]: https://rust-lang-nursery.github.io/rust-cookbook/concurrenc...


To me, this somehow says that most truely interesting problems can only be solved efficiently in Rust using unsafe. Granted, my definition of interesting may be a bit limited, but my initial takeaway here is that Rusts safety story is much more limited than its proponents claim.


> To me, this somehow says that most truely interesting problems can only be solved efficiently in Rust using unsafe.

Even Vec is implemented with unsafe code under the hood. Rust isn't about not using unsafe code anywhere: it's about encapsulating that unsafe code in reusable abstractions that can be easily audited. This is how essentially all languages work to begin with: unless you're using CompCert, the compiler itself is not proven correct, and so the compiler's code generator is "unsafe".

Here's an example of how rav1e, an AV1 encoder in Rust, builds safe abstractions out of unsafe primitives in order to solve a similar problem: https://blog.rom1v.com/2019/04/implementing-tile-encoding-in...


Wait, so how exactly is Rust better than C++ again? If I need to bring my own unsafe code to the party, where is the gain? Especially, if - in your words - "This is how essentially all languages work to begin with". I can build "safe" abstractions in any other language, too, can I not?


No, you can't build safe abstractions in C++. The language lacks the ability to enforce memory safety. Every abstraction you can come up with will have some way to subvert it. In Rust, the abstractions can't be broken without using unsafe, which application logic should never use.

This isn't just a theoretical distinction. Empirically, Rust programs have far fewer memory safety problems than C++ programs do.


In other languages, nothing ensures that those safe abstractions are actually used in safe ways. You can keep a dangling reference to a C++ vector, but Rust will make sure your references are valid, and you can only escape that with raw pointers in an unsafe block.


C++ doesn't have the tool to do so. Just like you can build abstraction for raw data in any language, but only the compiler in static typed language will scream at you at compile time if you use it the wrong way and dynamic language will just believe you will do the right thing.


In general you don't need to bring your own unsafe code to the party. The standard library contains most of the unsafe code you'll need to use in your normal code, and a handful of well-supported crates (like rayon) provide the more advanced utilities that the standard library doesn't. In most cases you're only writing `unsafe` if you're interacting with C.

And when you do decide you need to use `unsafe` for a non-FFI purpose, Rust provides the tooling to explicitly define the abstraction barrier such that clients of your code don't have to care (or even know) that you're using unsafe internally.


To reply to both you and your parent at once, yes, the reason this is in the nursery is that it's not a complete resource yet. I agree that this particular example is bad; it's showing the right pattern for the wrong example.

The right way is to use rayon, which is similar to OpenMP in this sense. Each thread would get a pointer to the chunk of the buffer it's operating on. You don't need to write any unsafe code to do this.


It's good to hear that there's an idiomatic Rust way of doing this that doesn't require unsafe but is still performant. I figured there was, but to an outsider it was not obvious how.

This is not actually the first time I've seen this example code. I was wondering how Rust solved this issue (parallel rendering into a single framebuffer) in a way that satisfied the borrow checker, and came across this example. It was a bit disheartening. Rust seems like an excellent language with boundless potential, but as I said, this particular example makes for a very bad first impression. It is also not obvious at all that this is part of the "nursery" and is not an official Rust document (the "Rust Cookbook" makes it seem more polished than maybe it is).


> It's good to hear that there's an idiomatic Rust way of doing this that doesn't require unsafe but is still performant. I figured there was, but to an outsider it was not obvious how.

Yeah, I mean, the reddit thread way up this comment chain talks about how Rayon should have been used, but the discoverability issue is real.

> It is also not obvious at all that this is part of the "nursery" and is not an official Rust document (the "Rust Cookbook" makes it seem more polished than maybe it is).

Yep; it's in the URL but not everyone will see that. Like most things, this is basically an accident of history; there was a push to clean this project up and make it an official resource, but before that work was finished, all the contributors had life stuff happen and had to go do something else. So it's in this weird quasi-state where there's a ton of good in there, but also some bad. We'll get to it...


Oh, someone on Reddit posted the rayon using code https://www.reddit.com/r/programming/comments/c7sgvw/writing...


Here's an article explaining how rav1e, an AV1 encoder aiming for production quality, solves this problem: https://blog.rom1v.com/2019/04/implementing-tile-encoding-in...


Oh nice.


I still don't understand why Rayon is not part of the Rust standard library.

Rust was created to have easy and safe multithreading on the CPU, and Rayon is the clear winner in this space, for me it feels like something that should be part of Rust.


The Rust stdlib is specifically intended to be as lightweight as possible (while still providing those idiomatic abstractions that might be needed throughout the ecosystem, e.g. std.future) in order to avoid the Python "dead batteries" problem.

What the Rust ecosystem is still lacking is a quasi-standard "Rust Platform" of best-practice library components where the community can freely deprecate something when a clearly better replacement comes along. There are a few "community" websites that try to provide guidance wrt. some parts of the Rust ecosystem, but nothing that feels even close to official or consensus-driven.


When I search for the Python dead batteries problem, the main issue is that the Rust developers don't want to maintain a growing list of libraries forever, especially that some of them will be depreciated over time.

This is totally understandable position, but it is still important to have sensible defaults for common tasks for end users.

One example that I can point to is Haskell, which I tried to learn from Haskell books, and it took me years to realize that it's not the language that's extremely inefficient, but the default libraries that use lists instead of arrays for most of the tasks.

I'm writing about Rayon because I see it as a reoccuring discoverability problem for people who are new to Rust.

There are many possible solutions, but one would be to have a default list of dependencies created in Cargo.toml that can of course be modified by the Rust users. Also that list can of course be changed over time by Rust developers.


Rust needs something like Rust dev blessed "extension packs", in my opinion.

Sort of like VS Code has extension packs.

Basically have some metapackages for common development areas, that pull in community vetted, high quality libraries for certain areas.


We have tried to suggest this, but the community has overwhelmingly rejected it, both as an explicit plan, and when people provided such a thing.


That's unfortunate. Whenever I talk about rust with my coworkers, getting crates through IT comes up as a major deal breaker. I imagine cooperate approved "packs" would make it much easier for us rust hobbyists to get some projects started at work in rust.

See python: my workplace (and I'm sure many others) is stuck with what's in anaconda for better or worse. The situation is certainly better than pure std.


why do they need to be dev blessed? You could do something like conda that's basically a distribution.


In my opionion the stdlib is a bit too conservative.

It is easy to end up with 200+ dependencies on more complex projects.

Some things should definitely be moved into std over the long term.

BUT: the time is not now. The language is still evolving rapidly. Upcoming features like specialization and a form of higher kinded types have the potential to impact API design a lot. I also really want named function arguments.

No-one wants to end up with a outdated and arcane standard library.


The main problem is that you cannot version the standard library (well, there are "editions" but that's a bit of a workaround for other language changes).

Go has the exact same problem, and has also historically made similar mistakes to Rust in their stdlib (the "syscall" module is strongly recommended against -- instead you should use "golang.org/x/sys"). And it should be noted that Go is even more bare-bones than Rust -- the lack of generics means you have to use "containers" which is very limited (I also have a feeling it's used by effectively nobody, because of the bad ergonomics).

I think the best solution for this problem might be some form of "meta-crate" concept which would allow you to pull in all of the well-vetted and stable libraries that most people want, without having to curate ~20 libraries yourself.


> And it should be noted that Go is even more bare-bones than Rust

In certain ways Go is more bare bones, but Go also comes with common hashes, some crypto primitives, encodings like JSON, compression/archives, logging, date/time utilities, Regex, a templating engine, an http client and server, regex, .... All of which requires a dependency with Rust.

I do remember an effort of a meta package like you mentioned. I think it was driven by brson and on Github, but I can't find it now, and it was abandoned anyway.


The problem is that several of those standard library components are no longer recommended for general usage:

* As with "syscall", users are strongly suggested to use "golang.org/x/crypto" for crypto primitives like AEAD. All of the crypto bits in the standard library are probably still "okay" but there are better interfaces and more efficient implementations in "golang.org/x/crypto".

* The "flags" package is basically useless for large projects (everyone uses "cobra" or "spf13/cli" to create CLI interfaces now). It also has the really weird Go-ism of "-flag" arguments.

* For logging, most people use "logrus" or similar. "log" isn't necessarily bad, but it's not full-featured enough that most people end up not using it.

* As for HTTP servers, at the very least you'll use "gorilla" to deal with routes -- if not completely switch to a different HTTP server implementation.

* Not to mention some of the other weird bits and bobs which are useful, but are strange to include in such a small stdlib, like "mime". Or some of the more fruity stuff like "database".

This is the downside of the batteries-included model. Especially when a lot of the bits in the Go stdlib were included early in the language's life and then quickly became a clearly bad idea but it was too late to remove them.


I agree and I'm not advocating for Rust's std to gain things like http clients or template engines.

But some parts of the ecosystem end up in a lot of projects. Conservatively extending std would be good for the language, IMO. To set a standard, reduce compile times, and ease the burden for companies that have hard review requirements for each dependency.

Some things I'd like to see in std in a few years:

* `serde`

* `rand`

* `log`, but probably without a backend, or a simple default

* a subset of `chrono`

* some parts of `rayon`


I think we could strike a balance. Don't put this stuff in the standard library, but have a category for crates that are officially blessed (or even maintained by) the core team, such that if you trust the Rust stdlib then you should be comfortable trusting these crates. Then provide binary distributions for the built crates for all tier 1 platforms at least (ideally for any platform where a binary distribution of the stdlib is available) and teach cargo how to download those. This way we maintain the ability to version these crates and have breaking changes, but without the overhead of having to recompile these crates.


What about a set of "blessed" crates (that can only depend on other "blessed" crates) that are maintained by the language developers? Seems like the best of both worlds to me.


Ah, I found the meta approach I mentioned: https://github.com/brson/stdx


There was also https://aturon.github.io/blog/2016/07/27/rust-platform/

Both failed to gain any traction.


To be clear here, yes, editions cannot change the standard library, as it must be the same across all editions.


If Rayon were in the standard library, we would never be able to make breaking changes, whereas in a crate we can possibly release a Rayon 2.0. For instance, we might want to make some low-level changes in the way iterators split their workload, per current experiments in rayon-adaptive: https://github.com/rayon-rs/rayon/issues/616


As somebody else wrote, what's really important is to stabilize a high level parallel iterator API that would probably be common even between Rayon 1 and 2. Of course I see some new APIs, like setting the task splitting policy, but some things, like parallel iteration over a vector and mapping through it should still work.

Java had to change the collection API when it added generics, but it showed that it's possible to split the API contract while improving on the underlying implementation over time. The low level Rayon primitives of course shouldn't be in the standard library name space.


Also, https://www.arewewebyet.org/ is somehow trying to provide a little bit of this but fails to actually recommend the best option.


What is the best option, in your opinion?


I find lib.rs mentioned in the article a good tool. Here on concurrency: https://lib.rs/concurrency




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: