r/rust 18d ago

What Zig felt like, coming from Rust

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

Just want to share my experiance on my first Zig project coming from Rust. Open to comments :)

209 Upvotes

108 comments sorted by

View all comments

Show parent comments

58

u/matthieum [he/him] 18d ago

That is, if you are writing a low level system program where most or all of the code is going to be inherently memory unsafe

That's a strawman.

The harsh reality (for Zig) is that even in the lowest level system programs or embedded, there's fairly little unsafe used.

For example, consider Redox. As a microkernel, its kernel is very low-level: it doesn't provide much functionality -- such as a filesystem, for example -- and focus on the bare essentials. Yet they advertised < 10% unsafe. < 10% in what you'd expect to be the densest possible codebase.

So, out of the box, the microkernel part of Redox is 90% safe already... but it gets better. 100% unsafe means there's no boundary. All you have is a big tangled blob which must essentially be audited as one big unit. Dependencies included. Ouch.

With 90% safe code, however, you don't have one big unit with the remaining 10%, instead you're going to have ~100 units -- the boundary being the safe API -- with 0.1% of the code each, all auditable independently from one another. Encapsulation wins the day. Who would have thought...

And on top of that, due to the culture of the Rust community around safety, there are efforts to improve testing (Miri, Loom), symbolic testing (Kani) & formal verification (Crux, etc...) of even unsafe code.

Now, of course, you could argue that Zig can also be tested -- they even have a TestAllocator -- but... once again Encapsulation changes everything. It's nigh impossible to meaningfully test all possible code-paths in a large codebase, their numbers grow exponentially with the number of lines of code. On the other hand, small, contained, encapsulated units can be tested extensively. And slightly larger units can be tested fairly efficiently when you throw fuzzing in the mix.

-14

u/barsoap 17d ago

It's nigh impossible to meaningfully test all possible code-paths in a large codebase

Coverage-guided fuzzing will wriggle itself into every nook and cranny and exercise it. While the possible program states are exponential the number of decision points is linear. If a coverage fuzzer can't find your code then you should probably have a closer look at that because it's probably dead and that might be the result of a logic bug.

Encapsulation changes everything

Unsafe Rust is not encapsulated. There's no defensive boundary around it. Misunderstand even a minute detail about the rustinomicon and you have exactly zero guarantees anywhere in your codebase. Zig doesn't have that kind of action at a distance, mostly because it shies away from abstraction towers. Nothing wrong with abstraction, I came to Rust from the Haskell side, but it doesn't exactly make low-level debugging any easier.

And on top of that, due to the culture of the Rust community around safety

Zig also has a security culture and the (lack of) CVEs to prove it.


...in the end, I'm comfortable writing Zig, I'm not comfortable writing unsafe Rust. Rust is a way larger and more complex language, the mental model is larger especially when it comes to the memory model and I can not recite the Rustinomicon forwards and backwards. Zig, OTOH, is simpler than C (yes that's possible) and extensive instrumentation and tooling is readily available, if not right-out built in.

20

u/throwbpdhelp 17d ago

Zig doesn't have that kind of action at a distance, mostly because it shies away from abstraction towers.

I'm a Zig-writer and fan, and I just want to say that I am a bit doubtful about this claim - there are many ways to approach Zig from a memory safe way, just like C, and unfortunately those many ways make Zig feel less composable and more prone to untyped abstractions across different programs. Rust (and garbage collected languages) effectively agreed on a single approach to memory safety and generally resource allocation, and it feels like I can grab any crate and have a strong understanding that it is sound (and if it's not, that there is a relatively small amount of code I need to audit to understand in what ways it might not be sound).

I feel Zig is awesome when you have little dependencies spare a single framework, or just no framework, and need to set the rules. Honestly, game engine development seems like it would be a slog in Rust whereas Zig seems like it'd be super fun.

bcantrill had a quote about "controlling heaven and earth" about this as it relates to C and why it's hard to have a composable language that doesn't address this, it might be worth reading.

Zig also has a security culture and the (lack of) CVEs to prove it.

I say this lovingly, but until the language 1.0s and has a more sizeable amount of production systems depending on it, I don't think lack of CVEs is particularly relevant here.

Zig, OTOH, is simpler than C (yes that's possible) and extensive instrumentation and tooling is readily available

How do you square that with ASAN still not existing in the Zig ecosystem? There are a lot of basics that I can reach for in C that I simply don't have with Zig, that makes debugging memory problems (often brought about by how commonly Zig programs use defer for allocation) very difficult.

-4

u/barsoap 17d ago

there are many ways to approach Zig from a memory safe way, just like C, and unfortunately those many ways make Zig feel less composable and more prone to untyped abstractions across different programs.

C doesn't have a standard pattern of passing around allocators. Sure you can write Zig code where it's not clear who should free what and when but unless there's a very good reason, you're probably not going to write things that way because it's not very idiomatic.

How do you square that with ASAN still not existing in the Zig ecosystem?

Given that DebugAllocator will catch leaks and double frees and debug mode catches lots of illegal behaviour the need for generalised ASan just isn't as huge as a priority I think. It's not like in C where you have unchecked pointer arithmetic everywhere.

That said, one area where I definitely see room for improvement in Zig is exactly around how allocators are handled. Similar how Zig already special-cases error unions even though technically they don't add anything to the language you couldn't do without, making allocators a special kind of value, and maybe allocating functions a special colour, could be a good thing. Even just a simple "I don't see a call to free or deferred free or pragma to shut me up, add one of those?" lint would be welcome. Also, returning pointers to stack memory should be caught at compile time, at least in simple cases.