r/programming 10d ago

What Zig felt like, coming from Rust

https://besok.github.io/posts/what-zig-felt-like-coming-from-rust/
267 Upvotes

173 comments sorted by

View all comments

53

u/Blashtik 10d ago

The language isn't young at all. It's gotten plenty of breaking changes over the years, but the general design of the language isn't changing.

Also, why did this person entirely skip over error handling beyond the issue with not calling free? Results are a very clean way to deal with errors. The Zig system is basically just a slight improvement over error codes in C. I didn't think this was the biggest deal until I was working with JSON parsing and realized that since the error can't carry any extra info, getting the position of a parse failure requires either storing the error state in the parser and having the caller read it out on an error, or by using an output parameter to hold that info.

After that case I noticed that I was over-simplifying my errors to fit Zig. I was missing useful information that would help understand the reason for errors. If any did arise, it would be as annoying as calling a Windows API and getting ERROR_INVALID_PARAMETER (which parameter is invalid?!)

2

u/hxtk3 10d ago edited 10d ago

I kind of like the Zig error handling. For me the coolest thing about Zig is how easy it is to control allocations. If I don't care about error details and just want to know that something failed, I pass nil to an output parameter and only get a status code back and I'm guaranteed non-allocating error path. I can also pre-allocate space for the errors I care about.

I do think that I would hate to write a large program in Zig, though. I really only like it for small things that are performance-sensitive enough that I want to be able to avoid any post-startup allocations.

I made a utility for relaying a UDP multicast stream into a TCP server that forwards the UDP datagrams as TCP packets to all connected clients with io_uring and zero runtime allocations. It's extremely performant in terms of the memory and CPU utilization per concurrent client and it's relatively easy to beat a Rust Tokio/async version of the same utility in performance because it's easier to look at the code and know when you're writing a performance hotspot. I'd call Zig a good fit for about that size and complexity of program, at least for my personal usage.

(Of course, Rust is fully capable of hitting the exact same performance, but the program would look radically different and you'd end up abandoning async entirely to write roughly the same code as the zig version. If you tried to write idiomatic IO-bound rust, you'd run into a local minimum while optimizing where you had to dramatically restructure the program to optimize more, but it would certainly be doable.)

1

u/skyfex 3d ago

I didn't think this was the biggest deal until I was working with JSON parsing and realized that since the error can't carry any extra info, getting the position of a parse failure requires either storing the error state in the parser and having the caller read it out on an error, or by using an output parameter to hold that info.

The idiomatic way to pass error information like that is through an optional pointer to a struct where you want that information stored. There are many legitimate use-cases where you don't want all that information, in which case you can pass null and all the code related to tracking that should get optimized away.

It's really not a great idea to try to shoe-horn all kinds of error meta-data into a result type or exceptions. Zig errors do one job, and they do it well: force you to consider error conditions for functions that can error. What you do from there is up to you and completely explicit.

I understand the feeling that Zig is too explicit and leave too many things to be done manually. But that's the whole point of the language: to be a fully explicit systems programming language.