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

Why should I use Zig coming from Rust? It doesn't seem that Zig actually solves the memory problems that Rust does.


Not fighting the borrow checker or making gratuitous copies of data to satisfy the borrow checker.

Zig's scope is just to be a better C that's free to add modern features like optional types, compile time expressions instead of string-macros, source level modules, packages, a more expressive syntax for writing bit-packed structures, a standard testing framework, deferred function calls, and so on. You can also directly include c headers directly in zig code, so this gives you a pathway to modernizing C Code incrementally.


> Not fighting the borrow checker

This isn't going to be appealing to Rust current users though, because “fighting the borrow checker” is a learning curve issue, you don't fight the borrow checker anymore once you've internalized its rules.

> or making gratuitous copies of data to satisfy the borrow checker.

You get it backward. In Rust there's less gratuitous copies, not more, because the ownership rules and the borrow checker gives you compile-time guarantees, while “defensive copies” are common in C++ for instance, “to be safe”.


That was not my experience.

I still fought the borrow checker after a year of using Rust.

And I found many situations while using Rust where I either needed to clone, or use unsafe where it's easier to make a mistake than in other languages because the syntax is extremely unergonomic and the memory semantics are much less clear.


> I still fought the borrow checker after a year of using Rust.

That's interesting. How much Rust did you use during that year? And how level of proficiency did you reach?

> And I found many situations while using Rust where I either needed to clone, or use unsafe

That sounds like an uncommon trade-off. From my experience, the trade-off was usually “clone or put lifetime parameters everywhere”, which is annoying (and I really wish Rust analyzer could help with at some point, because it's pretty mechanical), but it was quite rare, because most of the time you can just move things (that is transferring ownership), unlike in C where you have either a copy or a pointer.

> or use unsafe where it's easier to make a mistake than in other languages because the syntax is extremely unergonomic and the memory semantics are much less clear.

This is unfortunately true. Miri helps, but there's a wide margin for improvement on that front.


> How much Rust did you use during that year? And how level of proficiency did you reach?

What kind of answer do you expect to these questions?


For the first one, something between: “I was learning Rust on my spare time” and “I was working full time in a Rust-centric team”. And the second one is more subjective, about how confident you considered yourself with the language and how familiar you felt with its concepts.


I'd say that after using Rust for a year I also fought the borrow checker, but after using Rust for 6 years I definitely don't anymore. A lot of the difference between a year and 6 was unlearning C++ habits.


can you give examples please?


It's been ~a year. I can't remember. It usually had to do with the dreaded self referential structures, or interfacing with c libs.


For Self referential struct, the typical answer is twofold:

- try to design your code not to need them.

- if you really need one, you can either use smart pointers (Rc & RefCell) or use unsafe and use the Pin API to be sure that you won't accidentally move your struct.

That being said, it's not easier in C (or at least, it's easier to write, but also very easy to misuse), since a memcopy will silently break your self-referential struct. Do Zig has “Copy constructors” like C++ to avoid this issue?


In most situations those are horrible non-solutions.

Ref counted cells can't be used in self-referential structures because you'll introduce cycles. So, you'll go through all this trouble just to make memory leaks in the end.

"Designing your code not to need them" usually means implementing a worse version of a data structure from first principles.

The only real answer is to write "unsafe" Rust here and there. Which is fine. But, to the prevailing Rust community that is absolutely heretical.


> Ref counted cells can't be used in self-referential structures because you'll introduce cycles. So, you'll go through all this trouble just to make memory leaks in the end.

You know about Weak references right?

> "Designing your code not to need them" usually means implementing a worse version of a data structure from first principles.

> The only real answer is to write "unsafe" Rust here and there. Which is fine.

If it's a specific “data structure” (1% of the time) and not just the way you've decided to structure your current code (99% of the time), then using unsafe is fine.

> But, to the prevailing Rust community that is absolutely heretical.

No, the rust community is totally fine with reasonable unsafe code (you don't see burntsushi being harassed for using unsafe in his crates for instance), rustaceans hate (to a point which leads to excessive behaviors) unsound code, like the Actix drama, where the author used unsafe in unsound ways (not checking invariants in any way, and declaring “safe” functions that could cause segfault is used incorrectly). But unsafe code used properly is fine.


Yes, I'm aware of weak references and I'm aware that they're not a universal solution to this problem. You risk having something removed as unused while it's actually still being used if you don't have some strong reference to it somewhere.

As to your assertion that this is only '1%' of code, it really depends on what you're doing. If you're just gluing together modules that's where linear types for memory are an applicable model. But, if you are actually designing some structure yourself, it's not an applicable model.

The Actix 'drama' is reason alone to avoid the rust 'community'. I'll wait for these features to make it in a language with better ergonomics, a large commercial backer, and professionals at the helm who are less religious about how mere mortals use their language and are willing to err on being more pragmatic about it.


> Yes, I'm aware of weak references and I'm aware that they're not a universal solution to this problem. You risk having something removed as unused while it's actually still being used if you don't have some strong reference to it somewhere.

In graphs yes, but we're talking about self-referential structure here, and there you don't face this risk: the self-reference should always be a weak reference (the struct should be dropped when and only when the external references are gone, the internal ones don't count).

> As to your assertion that this is only '1%' of code, it really depends on what you're doing. If you're just gluing together modules that's where linear types for memory are an applicable model. But, if you are actually designing some structure yourself, it's not an applicable model.

Either you're implementing a data structure from a research paper (and you're in the 1% case and unsafe is fine) or you are actually using such a data structure and you don't have to perform this gymnastic by yourself. In C, people tend to reimplement data structures in their project because the dependency management story is quite dated, but it's not something anyone else does in other languages: most of the time you just import the standard library's data structure (or someone else's) and call it a day.

> The Actix 'drama' is reason alone to avoid the rust 'community'.

You'll find internet mobs in any group of people, unfortunately…

> I'll wait for these features to make it in a language with better ergonomics, a large commercial backer

If you expect larger backers than Amazon, Microsoft, Facebook and Google combined[1], I think you'll need to hold your breath much too long for your own good…

> and professionals at the helm who are less religious about how mere mortals use their language and are willing to err on being more pragmatic about it.

You're confusing redditors (the actix stuff took place on /r/rust) and the language management…

[1]https://foundation.rust-lang.org/members/


> In graphs yes, but we're talking about self-referential structure here, and there you don't face this risk: the self-reference should always be a weak reference (the struct should be dropped when and only when the external references are gone, the internal ones don't count).

Graphs are a common example of a self-referential structure.

> Either you're implementing a data structure from a research paper (and you're in the 1% case and unsafe is fine) or you are actually using such a data structure and you don't have to perform this gymnastic by yourself. In C, people tend to reimplement data structures in their project because the dependency management story is quite dated, but it's not something anyone else does in other languages: most of the time you just import the standard library's data structure (or someone else's) and call it a day.

On the several occasions I've look for crates that do what I need. They either don't exist, or I read through the code and they're poorly written and make lots of gratuitous copies of data and are very inefficient. You also have people using crates.io for their blog, literally.

> If you expect larger backers than Amazon, Microsoft, Facebook and Google combined[1], I think you'll need to hold your breath much too long for your own good…

A few people at large companies doing resume driven development isn't a real investment. It's not from the top down. At best, you could say they have some R&D investment to hedge their bets.

> You're confusing redditors (the actix stuff took place on /r/rust) and the language management…

Then why doesn't this sort of thing happen with other communities which are also highly online? It doesn't matter if you try to do gate keeping to say "oh those redditors" or "oh those guys on hackernews" aren't the REAL Rust community, the end result was a real package being really deleted from the real Rust community.


But the problems still remain? The borrow checker is an automated way of what one would normally check by hand, or in their mind. Removing it means that, just as one does in C, one must still check for memory errors and will more likely miss such errors more than the borrow checker does.


Zig isn't "watertight" like Rust, but the memory safety is still much closer to Rust than to C or C++, just because Zig enforces a lot more correctness in the language, and because of runtime checks. And a lot of memory corruption issues in C and C++ are actually side effects of C's and C++'s fairly relaxed approach to things like integer conversions, over/underflow, range checking etc... these are all points that Zig covers (for some of those things even when compiling C/C++ code because of the much stricter default compile options than regular C/C++ compilers).

(disclaimer: Zig has or had some pretty big holes around escape-checking function return values - that's something that even C compiler are warning about these days, at least for simple cases - but apparently improving this aspect is on the todo list).


Yes THAT problem still exists. Zig does not try to solve that problem (currently). It tries to solve a lot of other problems with C. Using the ownership model is one way of solving memory problems. There are others. I've seen discussions that zig could have an ownership model at some point, or maybe it will have something other. To lock in on that the ownership model is the be-all-end-all of memory problem solutions is a bit much I would say. Don't get me wrong, the ownership model is a good one and I like that Rust is pushing the frontier in that area, but there is room for other system languages that take other approaches to its design and tries to solve other problems first.


They have a build in arena allocator. That would cover a lot of manual frees. Also they have the defer syntax to defer your call to free.

The borrow checker doesn't stop most memory errors, just the easy ones at the cost of making it painful to write basic data structures. There are many memory related CVE on common rust libraries.


> The borrow checker doesn't stop most memory errors, just the easy ones at the cost of making it painful to write basic data structures.

This is completely untrue. Anyone who has maintained a large Rust project and a large C++ project knows that, empirically, Rust projects have far fewer memory safety problems than C++ projects do.


"The borrow checker stops only easy memory errors" is not the impression I have of Rust, and I would love to learn more about this claim.

For example, I'd love to see examples of CVEs on common rust libraries that you mention, if they are caused by safe code (ie, not using `unsafe`, which means the borrow checker is in effect).



You realize that these are notable because they're so rare, right? The vast majority of these would never even be filed as CVEs in C++.

Like, consider https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-4569...: "An issue was discovered in the mopa crate through 2021-06-01 for Rust. It incorrectly relies on Trait memory layout, possibly leading to future occurrences of arbitrary code execution or ASLR bypass." In C++ this would never have been filed, because it didn't actually cause any problems. It was only filed for Rust because Rust has much higher standards around even the possibility of potential memory safety problems than C++ does.


To add to that, I think the vulnerability is actually caused by unsafe code too:

https://github.com/chris-morgan/mopa/issues/13


Out of the 10 top ones in that list about Rust (there's one about C code in GCC),

1. 6 are because of unsafe code.

2. 3 are because of ouf-of-memory (OOM) errors.

3. 1 is due to unsafe code in `tokio`, but I think it's fair to consider unsoundness in tokio's safe abstractions (over unsafe code) as unsoundness in "safe Rust" because the crate is so foundational, even if its third party.

Regardless, I don't think any of the above support your claim that "the borrow checker only prevents the easiest memory errors".

I imagine one could argue that OOM vulnerabilities are less likely to happen in Zig because of its emphasis on fallible and explicit allocation, but that's still not an argument for the borrow checker only preventing the easiest memory errors I think.


The original claim was:

> The borrow checker doesn't stop most memory errors, just the easy ones at the cost of making it painful to write basic data structures

I'm not the OP but I think the point here is that the borrow checker is only applicable for the "easy" code (which does make up an awful lot of code), but even "basic data structures" very often require abandoning the borrow checker. In such a case, it's perfectly accurate to say "the borrow checker doesn't stop most memory errors" since you must turn it off when you write the code responsible for "most memory errors" -- at least that is the case in my experience.

So in this type of discussion, consider arguments about the capabilities of "the borrow checker" as analogous to arguments about "the memory safety of Rust in general", rather than taking it so literally.

If you want to make a sensible argument on this front, it should be about unsafe belonging mostly in crates that have been thoroughly looked over and battle-tested by the community. That is after all, a major reason why the number of such errors is still impressively small. But unsafe errors are still Rust errors, and while the borrow checker is wonderful, it and Rust should still be held accountable for problems in necessary code that requires disabling the borrow checker.

I think Zig does pretty well on this front, with there being pros and cons to not having a clearly demarcated "unsafe" boundary built into the language. Lacking an unsafe keyword does make it more unsafe, but in my experience, not very much.


I maintain Rust cryptography libraries with over 30-40k LoC, and I have never resorted to unsafe to bypass borrowck. Our code is as performant as any C impl, without the memory safety bugs.

There are many such libraries in Rust.


I wouldn't expect cryptography to need much unsafe. I've never worked in the area, but I expect code mostly falls into 3 domains: parsing, "business" logic (negotiating algorithms, checking certificates/signatures the right way, etc.), and actual crypto algorithms. I see great benefit for Rust in the first two domains. In the algo domain, I don't see how memory safety adds anything as memory issues are an insignificant problem. And there are big challenges like sometimes needing constant-time impls, etc., and how to do that without always having to dip down to hand-written assembly for each target platform. No language I know of provides help in getting bitslicing right, for example.

Again, Rust is great for an awful lot of code, but we should still assess Rust's memory safety claims against the data structures code where you often need to disable the borrow checker. An awful lot of people only use safe wrappers around well-tested unsafe code, which is great, but the unsafe code is still being executed and may still have memory "unsafe"ty.


> I don't see how memory safety adds anything [to algos in cryptography] as memory issues are an insignificant problem.

I'm not terribly experienced in cryptography either, but I'm pretty sure that memory safety is a HUGE deal in cryptography as a whole. Heartbleed[0] was a memory safety issue. Python's cryptography package switched to Rust specifically because of memory safety [1].

[0] https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2014-0160

[1] https://github.com/pyca/cryptography/issues/5771


Heartbleed was in the parsing/business logic areas -- the areas where I said Rust's memory safety provides great benefits. When I say crypto algos, I mean implementing S-boxes in 3DES, etc.


If you agree that Rust's memory safety is hugely beneficial in cryptography, then I'd love to hear other (sub)domains (e.g., kernel code, networking code, blockchain, ...etc) that necessitate tricky data structures, making Zig a better fit than Rust (which, IIUC, is your main point here).


I don’t have a “zig/rust is better than rust/zig” point here. The point was to say you have to judge Rust’s success against the unsafe code it occasionally forces you to write (you don’t get to hand wave it away), especially since a great example of such unsafe code is data structures code, and that is where a lot of my memory errors occur. Then we got diverted into some minor points about how much benefit does memory safety at compile time give you in different domains.

I wouldn’t build a company or long-term project around Zig right now, since it’s quite young, so if the choice was just those two, I’d probably always pick Rust. But ignoring that, since Zig will probably “graduate” in a year or two: both are good for all those domains, and I’d be hard-pressed to say one is better or worse than the other without further constraints. Zig certainly seems less indirect and more lightweight, and has less of an immediate cognitive burden, but Rust’s additional burden there is providing some additional value. I really don’t like Rust’s allocation story though — AFAIK there are plenty of allocations in std that can only be controlled by swapping out the global allocator, possibly even at link time. C++ is clunky on this front too, just in a different way. C is “better” simply because the standard library is so meager, but that has never been a problem for me in practice.


> You have to judge Rust’s success against the unsafe code it occasionally forces you to write (you don’t get to hand wave it away).

Agreed.

> I really don’t like Rust’s allocation story though.

I presume this is because of the lack of ergonomic handling of OOM? Only time I've run into OOM was when training deep learning models (in Python backed by C++) on a GPU, but if I were to do that in Rust, I'm pretty sure I'd be able to avoid the memory leaks that caused OOM since allocations in a deep learning training loop are almost always deterministic.

So I would love to learn about the domains where you think OOM is a huge issue (apart from kernel code, which I'm already familiar with).

Btw, thanks for all the replies! I'm learning with every one.


It's not at all about OOM, actually. It's about performance, and to a lesser degree, simplifying code or architecture. Zig's FixedBufferAllocator and ArenaAllocator are very simple and very useful, and they conform to the Allocator interface that's used for every bit of allocation in std and non-std. Anyone who has written a lot of C or a certain kind of C++ will be very familiar with the concepts, and will have used them a lot. They're nothing revolutionary, but not having a "default" global allocator and having a culture of passing the Allocator as a parameter means there's a nice way to actually use these things without always having to rewrite/modify someone else's code -- all Zig code I've seen passes the Allocator as an argument. This also means you have some idea of when allocations occur, which is another thing I often want to know (I used to work on ultra low latency systems, measuring in nanoseconds -- no allocations allowed in the hot path).


Gotcha. Again, thanks!!


> Lacking an unsafe keyword does make it more unsafe, but in my experience, not very much.

The problem is not "lacking an unsafe keyword", it's that Zig is not memory safe. Not being memory safe, in my experience, makes a massive difference.


> Zig's scope is just to be a better C that's free to add modern features...

The thing about that though, is there are a number of better C languages (both old and new) that have various levels of C interop. Languages like: D, Odin, Nim, Vlang, etc...


Eventually someone will make one that's ergonomic enough, provides enough features and can used to gradually refactor existing C with minimal changes code bases that it becomes a commercially viable alternative to just continuing to do maintenance programming on the C code.


I think there's three reasons one might use Zig instead of Rust. These are just opinions, and I would love yall's thoughts.

First: there's only a certain amount of effort that one is willing to put into accomplishing one's goals, effort that will be spent on learning tools and building a project. It's not unlimited. If I just want to make a game where a character walks around the world, and I only have 20 or so hours of time before the money / motivation / market opportunity runs out, and it takes 300 hours to master Rust, that's a non-starter. If someone has 2000 hours of time available, then it'll be worth it to learn Rust before starting. If someone has even more than that, then it might be even better to use more advanced proof tools to be even safer than Rust, if safety is an absolute priority.

Second: Zig offers a lot more freedom than Rust. The borrow checker historically has difficulties with observers, backreferences, dependency references, several forms of RAII, and graphs. If one just wants a struct to contain a pointer, sometimes it's just easier to use Zig than to change one's entire architecture, which takes a lot of time and effort that one might not want to spend. Of course, this isn't a problem if one uses Rc and RefCell more, which brings its own tradeoffs.

Third: There are a lot of cases where the cost of memory unsafety just isn't that high, and Zig's mitigations are more than sufficient. For example, if I'm making a non-safety-critical app or webassembly program that's sandboxed on the client's machine and only talks to its own trusted server, that's more than enough security for a lot of use cases. In these cases, a couple memory safety bugs are just like any other bug, and it's not worth it to spend the time reducing this release's bugs from 23 to 21. This is how it was on Google Earth; the vast majority of our bugs were logic bugs, not memory unsafety (and this was C++).

Coming from Rust, the first point likely doesn't apply to you as much. The second and third points might apply, depending on the situation.

I think Rust's tradeoffs are stellar for cases like medical devices or HFT where the cost of memory safety and higher latency is much higher than your average webserver (where Go might be a better fit) or your average single player game (where memory unsafety bugs are just inconvenient, not critical).

If I was to sum it up, I'd say: Rust is pretty close to perfect on paper, when you don't consider the other human factors, such as limited time, training, or that sometimes it isn't worth it to prove memory safety. When you consider these factors, other languages can make more sense.


> Third: There are a lot of cases where the cost of memory safety just isn't that high, and Zig's mitigations are more than sufficient.

But then why not just use a GC'd language? It isn't 1995 anymore; most apps are written in GC'd languages. C++ makes sense when you already have a lot of C++ code, but if you're talking about new code, I don't see a lot of room for a language without memory safety.


> But then why not just use a GC'd language? It isn't 1995 anymore; most apps are written in GC'd languages.

I mostly agree with you, and I'm enthusiastically using Rust. But I'd love to be able to develop new software, with modern developer conveniences, that could run on a 1995 computer. Assuming that implies a 32-bit OS, that seems plausible with Rust, albeit not with std (but alloc should be fine). Maybe I'll play with that for a side project sometime.


Rust has already been made to compile stuff for 1995 systems, if only as an experiment: https://seri.tools/blog/announcing-rust9x/ / https://news.ycombinator.com/item?id=31112273



That was a typo, supposed to say "memory unsafety". Thanks for catching that, edited now.


for native addons for GC'd languages you probably don't want to introduce another GC, rust is not well-suited for interfacing with FFI, C++ is a bit heavyweight but okish I guess and C is totally unsafe, Zig is just right.


> rust is not well-suited for interfacing with FFI

How so? Packages like neon [1] and rustler [2] suggest otherwise. I'm using both of those in a real product (I'm using neon directly, to write native modules for an Electron app; on the back-end, I depend on an Elixir package that uses rustler).

[1]: https://github.com/neon-bindings/neon

[2]: https://github.com/rusterlium/rustler


I admit it's a long time since I've been using neon so things might have changed but at the time I had a feeling that the whole thing is very fragile and not really safer than if I was using pointers directly.

Especially, if you callback into JS and that calls back to native then I think you don't have any guarantees from rust anymore (aka you are being lied) because compiler cannot not know about that.

Also builds were a bit tricky, it needed node.js headers in proper place, etc. In zig you just include napi.h because zig can import C including macros and everything. Or if you need to call glfw/nanovg/stb/xxx you just @cImport() and it works.

Zig also supports C-style strings in a better way and I can't recall all of the obstacles right now but I'm way way more productive with Zig after 1 month than I was with rust after 5ys.


> rust is not well-suited for interfacing with FFI

That's a surprising comment, given the massive deployments of hybrid Rust/C++ apps at huge scale that exist!

Does Zig have any equivalent to cxx for interfacing with C++ code?


you are right about cxx, I made this comment with C FFI in mind. and for that, Zig couldn't have better integration.

AFAIK there is no simple way to interface with C++ from Zig, you either have C headers or you write a bit of C++ to export what you need and then you're back in the game.

Or you flip it on its head and use zig as C++ compiler and call your zig-parts from C++ but I have no experience with this, I think bun does this.


One should keep in mind that Rust also has an unsafe subset. While it does not get rid of the borrow checker (quite intentionally) and it also introduces some pitfalls wrt. interacting with safe Rust, compared to C, Zig, Hare, Jai etc. it is in principle quite possible that future versions of unsafe Rust might become just as ergonomic as these competing languages.


(Tiny note, unsafe is a superset, not a subset.)


Despite what students who never programmed professionally say, memory isn't a problem to a significant amount of C and C++ developers. I think it's been a year since I last access an invalid pointer and that only happened because I forgot that I am not suppose to add elements to array in a `for (auto var : array)` loop. It use to be a for index loop and it didn't take long to figure out the change broke something


I'm one of those who "has never programmed [C/C++] professionally".

Isn't your statement at odds with assessment from many big tech companies that memory safety is the prevailing reason for CVEs? I'm sure you're not suggesting that the folks who make these assessments are not experienced C/C++ programmers, so I'm curious about your thinking on this.


That's the wrong question to ask. Don't compare it to rust's memory safety as otherwise you're pretty much answering your own question with "you shouldn't".

Think of what zig has to offer instead and try it out to see how it works.


Compile time speed (this is gonna come later with a full hosted compiler, without LLVM)




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

Search: