What Zig felt like, coming from Rust

Oh god you’ve brought back memories of doing http download on a microcontroller.

First though:

Thank you for that code snippet, it makes anything else I see feel less awful by comparison :slight_smile: .

Agreed on the ‘option bag’ being much nicer, and in the http library I work with, that is in fact how you specify them:

esp_http_client_config_t config = {
      .host = CONFIG_EXAMPLE_HTTP_ENDPOINT,
      .path = "/get",
      .query = "esp",
      .event_handler = _http_event_handler,
      .user_data = local_response_buffer,
      .disable_auto_redirect = true,
};
esp_http_client_handle_t client = esp_http_client_init(&config);

esp_err_t err = esp_http_client_perform(client);

esp_http_client_cleanup(client);

Which works fine, but if you want to download multiple files from the same server then it’s really inefficient because:

  • The TCP connection is killed and reopened for every request.
  • You tear down and rebuild the entire HTTP engine each time, including all the memory and buffers and such.

This is why those ‘set’ functions are necessary: it lets you use a persistent connection for multiple requests:

esp_http_client_set_url(client, "/get2");

Or even:

esp_http_client_set_url(client, "/put");
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_post_field(client, post_data, strlen(post_data));

Either way, method-chaining is the absolute worst way to do this. How are the intermediate functions supposed to report an error? Exceptions? Yeah no, we disable those for a reason.

I could rant more, but this guy does it much better:

Funny you mention that! I brought it up because when I was in university, I had just written an AVL tree in C for a class and tried writing one in Rust to learn the language. But, being a beginner, I heeded the general advice to stay away from unsafe. I tried using references and it was not a good time.

But your code shows it isn’t so bad with unsafe. And it’s much more readable than Rust’s BTreeMap.

A really good write up. I appreciate that the author came with a measured approach, understanding that idioms from Rust might not translate to Zig and being okay with that.

2 Likes

Generally, I wonder why Zig is so often compared to Rust, while its much more influenced by C and it’s derivates and often called kind of a C successor (at least, that’s what you read very often); or to be more precise the comparisons to Rust get much more attention.

Is it just because Rust and Zig are relatively new compared to all C languages/forks? Or is there something more fundamental which fuels this comparison? Even more strange that those Rust-Zig discussions can get really heated (just think of the whole “Bun to Rust port” drama) while comparisons with C style languages are much more chilled.

1 Like

IMHO Zig is closer to Rust than to C, so it makes sense to compare Zig to Rust (or even C++) rather than to C.

E.g. look at things like optionals, nullability, error handling, the explicit casting requirements, slices, generics, usize in the most inconvenient places… :wink:

These are all features where Zig and Rust have solutions that sometimes overlap and sometimes diverge, while C lacks most of those things completely.

The ‘other’ language I would add to make the language threesome complete is Typescript btw :slight_smile:

PS:

while comparisons with C style languages are much more chilled.

The C community is probably the most chill of all, probably because you usually only arrive at C after having tried pretty much everything else, so people know exactly why they picked C for a specific project. And there’s also not much to discuss about C and no money to make, thus no ‘C conferences’ or ‘C influencers’, and thus no ragebait on YouTube or social media :wink:

10 Likes

Probably because many people who are using Rust have already chosen not to use C/C++, so they are also interested in other alternatives to C/C++ such as Zig. They are two good choices, with very different trade-offs.

5 Likes

Good question.

Imo spiritually Rust is a lot closer to (modern) C++ than it is to any kind of C (with maybe GObject like C as an exception, but let’s not talk about that mess), unlike Zig which is closer to C than it is to any kind of C++.

4 Likes

This is true, but I would also say that Zig is much closed to C++ than Rust is closer to C++ :stuck_out_tongue:

C  Zig     C++               Rust

Both C++ and Zig extend C with a higher-order mechanism to “generate” new first-order code, C++ using templates, Zig using comptime. The result is pretty much the same ---- we can stamp multiple instances from the same “template” function, specialized to specific types, and it is the specialization that is statically checked. Zig just cuts a much more direct path to that result. Zig is what C++ should have been. (Historically, C++ was defined by this idea of allowing the user to implement user-defined types whose usage syntactically is indistinguishable from builtin types, but that’s not what turned out to be useful in practice)

Rust is different. It tries to treat “higher order” code the same as “first order” code, type-checking at declaration time, rather than initialization time. It focuses more on modeling via explicit interfaces, rather on generating specific machine code.

14 Likes

To me, Rust often feels less like fancier C/C++ and more like a low level typescript/haskell.

3 Likes

It depends on what kind of scale you use to compare them.

I would scale them like this:

C         Zig                     Rust                     C++

everything is slop now, it’s quite depressing.

7 Likes

I found the resulting Zig code less readable than its Rust counterpart.

As I was reading through the comparisons, even as someone who spent several years writing rust, all I could think was “Man this rust code is so hard to read.

I guess it just goes to show somewhat that ‘readable’ in some sense is really just a stand in for personal preference and may not actually be a fully observable metric. IE we can capture data points related to readability, but we can never fully enumerate or quantify all inputs into definitional, and quantifiable ‘readability’.

3 Likes

In my experience, it’s less that “Rust is more readable” and more that, declarative-style code is more readable in general, and Rust has some tools people can use to write declarative code that is otherwise impossible in Zig (mainly because of a lack of lambdas)

Like anything, if you’re only used to pure imperative code, mainly-declarative code will feel very weird. It’s especially hard to try to think up how to write a piece of imperative code in a declarative manner. But it comes with time and even in Zig it does usually result in easier to read code.

Another thing I should mention is that, as much pain as Zig brings by making allocations explicit, Zig can also make it “vanish” if you just use an arena allocator, or a “Pool” style container with indices instead of pointers. In practice, at least in the fields I’ve worked in, few things truly need a fully dynamic allocation system and it’s almost always better to “allocate and forget” and then free everything in one go at the end.

3 Likes

I think this is an important point that not a lot of people are talking about.
On a logical level, it’s easy to accept that the language maintainers should be allowed to make breaking changes in the interest of making the language more perfect.
The changes made to the language and standard library are also justified with explanations that are also logically sound and very easy to accept.

But our blessing and our curse is that we’re humans and not Vulcans, and so we get a kind of paradox where even viewed through a purely logical lens, we have to accept that emotion is weighted equally with logic in decision-making, and so we’re committing a logical fallacy if we don’t admit that people do illogical things.
To state that less philosophically - you can understand and accept that the language should evolve, but that doesn’t make it less frustrating when you want to use new features, but the upgrade process is painful.

This creates a point of friction which is actually relatively strong.
Most people will want to use shiny standard library features like std.Io, but might depend on a Zig library that stopped getting maintained years ago, because Zig has been around for a long enough time that “ghost town” libraries like this exist.
Forking this library (something you probably don’t want to do in the first place) and rewriting it to use std.Io is likely to be very non-trivial, but you don’t have any other choice, except for downpatching the Zig version of your project.
You actually can’t even win, because supposing you have another dependency like zignal which actually is permanently maintained, downpatching isn’t an option for you either without forking the library that gets updated to… “downdate” it.
This also means that anyone who publishes a library isn’t really “correct” to either abandon their library or keep updating it, because both of these approaches can run into issues.
In theory, it would be perfectly correct to keep updating your library if everyone else in the world also kept updating theirs, but that isn’t going to happen.
Your best option as a user, when encountering an outdated library, is to hope someone has bailed you out and published a fork which they had to update themselves to get it working with “new Zig”. Unless this fork was made a few versions ago and the disgruntled user who made it wasn’t interested in updating it. Which is quite likely.

While all of these issues surely exist in other languages, they’re exacerbated in Zig because the language maintainers have given themselves the freedom to break the language so it can be remade more perfectly.
This creates an emotional, if illogical, reason not to use Zig for your project.

5 Likes

When dealing with an unstable environment like Zig itself and the Zig ecosystem, your best bet is to own the full dependency chain. That is, be very careful what you depend on. And when you depend on it, you treat it as if it’s under your control. Be prepared to fork it and fix your own bugs, because the chance is, in the next year, there will be a difference between the version of Zig the maintainer is using and what you are using. If you look at any bigger Zig project, they are on old Zig versions, and they own their dependencies. They often even have their own mini version of stdlib.

9 Likes

I know this is a sensitive topic but having an LLM port one of your deps to the version of Zig you’re a using is quite helpful. It doesn’t have the usual drawback of LLM code, because upstream will eventually be updated and you can discard the LLM code.

6 Likes

Yep, this is very much true. I’ll be completely honest, if it wasn’t for LLMs, I would have abandoned Zig soon after learning it. I wasn’t ready for the level of breaking changes. I’ve not seen this anywhere else in the open source world, and I’ve been part of it for 25+ years.

4 Likes

I think it’s just timing, domain and human insecurity.

Rust has been pushed as a “systems programming language”, which seems to mean anything lower level than Java, to the point where it is now accepted in the Kernel. Zig (another “Systems PL”) is now getting it’s ascendance, and I think a lot of people get threatened that there’s something else which isn’t what they’ve invested time into. They have to attack to justify the camp they find themselves in.

IMO the languages occupy two different levels on the abstraction tree. Rust is clearly higher level with it’s type system. Zig is lower level. As such they should be able to co-exist. It’ll calm down in time.

9 Likes

Hard disagree and for exactly the reasons stated in this thread. It’s illogical to build serious software on top of an unstable ecosystem unless you believe the advantage to be much larger than the cost of the churn, which I think is unlikely because regardless of what language you’re using, the language is just there to inform a compiler how to produce a binary. It’s also unreasonable to expect devs to have to change their code every time you decide you don’t like the API you’ve chosen.

But zig is not even near v1.0 and that’s what I think makes it acceptable to have breaking changes. We’re beta testers so when we get bit by the churn, it’s on us.

3 Likes

My point of view: It’s illogical to build serious software with arbitrary third-party dependencies on top of an unstable ecosystem.

1 Like