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

I've been amused to watch how Rust now does a simple blocking HTTP request. A few years ago, you used the "hyper" crate, which was a convenient wrapper around the "http" crate. Now, you're supposed to use "reqwest", which is a convenient wrapper around the "hyper" crate.

"Reqwest" uses the Tokio machinery, even for a blocking request. If you turn on "Trace" level logging, you can watch it start up a thread pool and go through a 35-step process, using all the async and futures machinery, to do one synchronous request. Log messages include "handshake complete, spawning background dispatcher task" and "signaled close for runtime thread (ThreadId(2))"

This seems excessive.



> A few years ago, you used the "hyper" crate, which was a convenient wrapper around the "http" crate. Now, you're supposed to use "reqwest", which is a convenient wrapper around the "hyper" crate.

That's not right. http is supposed to be a common library of types for HTTP servers and frameworks (although developers of some competing frameworks have rejected it). It was never an HTTP client like make it sound like, and it's actually newer than hyper.

As for reqwest vs. hyper, the former offers synchronous wrappers over the async ones, easier TLS support and other niceties (compression, proxy support, cookies, WASM). It's high-level and easier to use, somewhat like requests over urllib3 in the Python world.


But then that is nothing inherent to Rust, it's a choice made by a particular external library. That is probably the worst downside of async, it splits the ecosystem. Reqwest probably tries to offer both choices by hiding one behind the other (although what you describe sounds excessive - running a single task with tokio's single-threaded executor is actually quite lightweight).

That split between "sync" and "async" was always there, for networking libraries. E.g. on C/C++ you find libs that run on libevent, or Boost.Asio, or other varieties, and they don't mix, so you end up spawning a seperate thread - exactly the same thing.

And this is, IMHO, how we should see Tokio + async: as a more ergonomic libevent.


> it's a choice made by a particular external library

Yeah I think it points to a culture problem. In some ways because dependency management is so easy with Cargo, I think it creates the temptation to just throw in some dependencies to make something work without truly understanding the overall complexity of what you're creating. Something very similar happens in the NodeJS world.

> it splits the ecosystem

This is something I've really noticed with Rust: it almost seems like there are really two things: Rust, and Rust+Tokio. I'm a bit ambivalent about Tokio being baked into so many libraries: I think it's great to have as an option, but once I decide to use one library built around Tokio, it imposes a lot of constraints about how the flow of control is going to work in my program.


I think rust is kind of a perfect language for being profligate with dependencies, because the safety guarantees, typing, etc make it very hard to misuse a library, and relatively easy to design a library that is hard to misuse.

A lot of what is not enjoyable about rust as a user is really nice when it's being imposed on people who are not you, whose work you're interfacing with.


Just because a library is safe does not make it good. To the point of the previous poster, you might for instance have an http library which does a lot of unnecessary async work behind the scenes to do a simple synchronous request.

If we all have the attitude "it's good/fast because it's rust" this is not going to lead to a lot of cruft making its way into the ecosystem.


I think if a dependency is a perfectly sealed abstraction, where a complex function is reduced to a simple one with no leakage, then there's no reason not to use it.

Obviously, in the real world, this basically never happens. Performance is one thing that's basically always going to 'leak', so you still get people rewriting stuff in assembly, or making custom asics, because the abstractions that higher level languages offer are not perfect.

In a strongly typed language, with strong safety guarantees, I think there are less ways an abstraction can leak (for instance, by corrupting memory or whatever), so there's a correspondingly lower cost to pulling in a dependency than there would be if you were working in an unsafe language, or a dynamic one.

I also think if performance is the only way in which your dependency leaks implementation details, then it still makes sense to pull in a dependency first, profile, then swap out if necessary.


Agreed with your point about Cargo. It's a double edged sword.

We absolutely have an NPM/leftpad culture in Rust.

Is that better or worse than C and C++ where dependencies are so painful that you end up reinventing the wheel most of the time? I honestly don't know.


Yes I think it's a really difficult problem to be honest. I am definitely grateful for how easy it is to make rust projects reproducible, but it's not without disadvantages.


Dependencies are relatively easy, actually. Just most don't bother learning how to do it, and do it PHP style with header includes.

On UNIX systems just using pkg-config and similar tools, or just adopt either conan or vcpkg, which contrary to cargo also support binary libraries out of the box.

Plus vendoring C and C++ libraries is not a dark science, only known by old druids.


Maybe I've just missed it, but I have found pkg-config difficult to use and poorly documented. It's fine if you are installing things with a package manager, but I found it took some trail-and-error to figure out how to do this for my own lbraries, or for things built manually from source.

Also with c/c++ style system dependencies, I feel like there are a lot of issues with things like version conflicts which are solved much more simply by a package manager like cargo.

I agree that it's functional, but to say relatively easy I think is a bit of a stretch.


Cargo also has issues when two crates have incompatible dependencies, or at very least you end with the same crate being compiled a couple of times, as the hashes don't match up.

Usually when compiling from source many libraries provide pkg-config configuration files on "make install".


Yes. Rust encourages version pinning. You go to "crates.io", and it gives you a specific version number to put in your "cargo.toml" file. Now you're nailed to that version for your program or crate. Crates have their own "cargo.toml" file, with their own versions, and it's quite possible to pull in multiple versions.

Right now, I'm using the latest version of "reqwest" known to "crates.io." It's pulling in Tokio v0.2.23, not the new tokio v1.0.0. No surprise there, the new version only came out yesterday. So we'll see how the new version works at some time in the future.

It's good to get to version 1. The semantic versioning rules allow breaking changes without changing the first digit when the first digit is 0. Typical complaint on forums: "bignum happens to use rand internally, and it happens to only use version 0.5.0, with restrictions against using a higher version due to breaking changes." Rust still has many low-level crates at version 0.x.x, from "bytes" to "uuid". Reaching 1 indicates greater stability.


> Yes. Rust encourages version pinning.

Rust makes version pinning feasible, e.g. by allowing multiple versions of the same package in a build (not all module systems have this feature!) but doesn't encourage it. You've identified a problem with using 0.x.y-versioned packages as dependencies (which means de-facto opting out of semver), but that's not a problem with Rust specifically; it could occur in any language.


To be clear, the default semantics are ^, not =. So even if you put 1.2.3 in your Cargo.toml, you may get 1.3.0, you just won’t get 2.0.0.


Which might lead to the same outcome anyway as there is nothing that prevents those version numbers to actually mean anything.

It is up to the library authors to uphold its semantics.


But with Cargo it's scoped to the crate you're compiling right? So it only matters if there's a collision in the dependencies of a given project.

Isn't it the case with pkg-config that everything is stored in a central location?

In any case, I think you can't seriously argue that the c/c++ dependency management solution is anywhere close to running `cargo build`/`cargo run` in terms of simplicity.


Yes I surely can, because Conan and vcpkg do exist, and contain all major well known libraries in the C and C++ communities.

Specially the C++ community has seen lack of something like cargo as weaknesses and moved to sort it out.


Yes I had the same reaction - it seems unreasonably complicated to do a simple, blocking HTTP request in Rust.


Give ureq a try: https://github.com/algesten/ureq It is blocking only and has a pretty simple API.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: