18 ms·
I love both Zig and Rust - I believe they both serve different types of systems programming - but for many developers, I'm not sure how important these points a
by jkelleyrtp 6y ago
I love both Zig and Rust - I believe they both serve different types of systems programming - but for many developers, I'm not sure how important these points are.
Zig's reasons for being better include:
- No operator overloading
- Rust panics when allocator fails
- Rust uses a std allocator (which is configureable)
- No metaprogramming (IE, macros)
- Simpler to grok
Essentially, Zig being the "Go" of C/C++/Rust/D. But, I think that dances around the problem where Systems Programming is just complex. Just because some functions and control flow is simpler doesn't mean the program itself is.
If trying to pull Rust programmers away, I'm not sure the merits in this article are strong enough. However, for newcomers to systems programming, Zig might be easier to learn and solve problems with.
- deleted 6y ago[deleted]
- slezyr 6y ago> - No operator overloading Ehh??? It's the first point there. And I don't like all this hate for operator overloading. I prefer to write "vec1 * (vec2 + vec3)" instead of "vec1.mul(vec2.plus(vec3))".
- whatshisface 6y agoOperator overloading is hated among programmers who never use math; in that community all uses of "+" are to do something other than addition, usually something that's not even remotely related to addition. Operators end up as one-character infix method names.
- colejohnson66 6y agoI don’t think I’ve ever seen a library overload math operators when they didn’t do what you’d expect. And just because a language feature is abused doesn’t mean it’s bad. Typescript’s generics are Turing Complete, but they’re still great.
- whatshisface 6y agoLook at it this way - let's say you're a programmer who never uses math. As a result, 100% of your exposure to operator overloading will be, by definition, to cases that have nothing to do with math. While a programmer that uses math will see 1,000,000 good uses and 10 bad uses, a programmer who never uses math will see 0 good uses and 10 bad uses. I saw a similar effect in the Go community where they were debating whether or not the complex type should be removed. It's absolutely crucial for signal processing, but to the rank and file REST-ist, it's just another annoying case to type out in a generic-less language.
- Quekid5 6y agoWhat programmer never uses math? I mean, most of us aren't going use linear algebra, but still...
- whatshisface 6y agoI'm using math in a colloquial sense to refer to "mathy math," not "computer science math." So linear algebra would be a leading example, along with stuff like vectors and complex numbers. Accounting math would be another example, because it requires a bignum implementation that doesn't drop decimals like float can.
- Quekid5 6y agoAh! Might be helpful to specify that up front. Even so: Interval math could be quite helpful for lots of things that ... aren't using it.
- hnlmorg 6y ago> Might be helpful to specify that up front I thought his post was pretty clear tbh. For example he mentions complex types so that should be an indicator that he's not talking about the kind of high school maths that the average developer can coast along with.
- munk-a 6y agoI'd rather solve this with code standards around function naming conventions (including symbols as names for functions) than remove a language feature. I don't override operators often, but when I do it's useful because the operation I'm describing is a really close parallel to other uses and properties of that operation. As an example, it's why I dislike + for list append, + is normally commutative (which list appending definitely isn't) and subtraction is pretty bonkers as a reflected operator on lists.
- a1369209993 6y agoFWIW, I advocate ~ as array append (a la perl6/raku strings) (and also as wedge product on vectors, * being dot product). It works well with x^y as exponent/repeat, since the rhs is qualitatively different anyway[0], and also gives you (with +) a sum-of-products/dioid/semiring structure for concatenation and alternation on regular expressions. Admittedly, you still don't have a sensible semantics for division, but that's true of plenty of other product operators (even int is iffy - either not closed (div-as-float/rational/etc) or not a multiplicative inverse (divmod)). 0: In particular, exponentiation by natural numbers (or positive integers for things like nonempty lists that deliberately exclude the multiplicative identity) is almost always well defined[1], even if the thing you're exponentiating bears no resemblance to a natural number. 1: Although note than with vectors and dimensional quantities (eg meters), x^2/x^3/etc are all different types: area/volume, bivector, etc.
- codeflo 6y agoI get your point. But even in non-mathy business software, data structure access is so much more convenient in languages with an overloadable index operator: C#: foo[index] = bar[index]; Java: foo.put(index, bar.get(index));
- whatshisface 6y agoThat is a very good point that I hadn't considered, because I was thinking about Go. In Go, the builtin data structures are compiler special-cases, so you get the index behavior on the map structure. Of course, it starts looking like Java for anything more advanced, but the ethos of Go is to not use anything more advanced.
- hctaw 6y agoThere are more operators than the infix mathematical ones, like increment/decrement (cursors, iterators, custom counters), dereferencing (used for some clever pointer types), etc. Sometimes there are good reasons to mask complexity.
- mhh__ 6y agoI for one have literally never been bitten by it anywhere. I get that you could launch missiles with it but ultimately that's true of literally any structured programming.
- mcintyre1994 6y agoI was once trying to diagnose a performance issue in an algorithm written in Ocaml. Someone had overloaded ** to be (IIRC) 64-bit multiply. I had a momentary “gotcha in 2 seconds!” moment before realising what was happening with that one.
- andrepd 6y agoFunny you should mention OCaml, with doesn't have operator overloading! You can only have one function with a given name in scope at any point, that includes operators. Of course you can redefine with `let (+) a b = ...`, but then you have to explicitly open that module where you want to use that redefined operator. That makes it even more clear what's going on.
- electrograv 6y agoAllow me to introduce you to C++’s new std::filesystem::path class, which overloads the divide operators "/" and "/=" to concatenate two paths. (Not to be confused with the “+=“ operator, which also concatenates paths, but with subtly different behavior.) Why did the division symbol get chosen for a concatenation behavior, of all things? Well, I suppose because it looks kind of like how we write paths with slashes, e.g. “dir/subdir/etc”. While I understand how this might be fun aesthetically once you already know the types involved, I find this completely opposite-than-numeric behavior here to be quite ambiguous and unintuitive.
- edflsafoiewq 6y agoPython's pathlib does that too. It doesn't sound like it's bitten you though; it's just an aesthetic disagreement.
- deleted 6y ago[deleted]
- Impossible 6y agoI think this is the main reason. Outside of math libraries (and maybe strings for concatenation?) operator overloading rarely makes sense. If you're working in a math heavy domain with custom types it's a godsend though. I wouldn't want to work in a language without operator overloading (I've done enough work with Processing and a Quake derived game engine to know that it sucks), but others might have seen it horribly abused
- gnramires 6y agoAnd I would go even further! I have been thinking recently that everything should (or rather could, in a special language) be overloadable. And every operator should be treated as a non-first class citizen of a language, '=', '+', and all. The inelegance is granting those operators any privileged status. So the parsing can be in every case dependent on the arguments being parsed: if you're adding numbers, number addition, if you're adding vectors; even more complex behavior could be added for special parsing cases (which I don't even know what could be), say for creating certain algebraic operators with special conditions, maybe something like knuth's up-arrow. Of course, thought must be employed on the scoping of these parsing changes but since this behavior would be conditional on the argument properties, it is very difficult to see any problems. For example, while adding vectors overloads '+', it is not going to cause problems in other cases (when the arguments aren't vectors), and when dealing with vectors 'vec1+vec2' the programmer will essentially always be thinking about the overloaded operation anyway, it seems absurd a confusion would occur. I should be able to write 'x=5!+3' to mean 'x=factorial(5)+3'. Allowing contextual meaning (evaluation) of operators, functions, even syntax, allows more compact, expressive language, because we can reuse words, associate their slightly different applications and adapt syntactical behavior to the problem at hand. Take the usages of the word 'slow' in natural language: it can be a description of current velocity (variable) of an object (context-dependent) "the car is slow", it can be a description of a property of an object (low typical/maximum velocity) "slugs are slow", it can be a verb "please slow down", and so on. Creating new words for each use case is inefficient and disregards the natural close association of their (contextual and algorithmic) meaning.
- pjmlp 6y ago
- hansvm 6y agoI know a lot of people don't like overloading in general because they've been bitten by people abusing it, but I don't think that's where Zig is coming from. Rather, the project places a high value on being able to understand a snippet of code in isolation, particularly the performance implications of that snippet. Especially in math-heavy domains I don't think anyone is arguing that "vec1.mul(vec2.plus(vec3))" is cleaner code in the abstract (and I know I've personally written pre-processors _entirely_ to avoid having to write that kind of garbage when doing math-heavy code in an environment unfriendly to such syntax), but the function calls make it crystal clear that something non-trivial is happening under the hood. Do I want to to give up operator overloading in Python? Absolutely not, and to be frank I wish that portion of the language were even more dynamic. Do I care about Zig not having operator overloading? Not in the slightest. It sits at a different point in the language design space, and I'm super excited about it.
- creata 6y ago> the function calls make it crystal clear that something non-trivial is happening under the hood. Well, it makes it crystal clear that something non-trivial may be happening under the hood. If I have a vector type which is implemented using SIMD intrinsics, I'd still call its addition operation "trivial", even if it hasn't been blessed by the language as such.
- hansvm 6y agoThe point that we only know something non-trivial _may_ be happening is definitely fair :) I'll leave the original comment as-is since you've already responded. While we're nit-picking, a wide vector type implemented with SIMD intrinsics would still have a non-trivial addition as far as Zig is concerned; your specific example really only holds for sufficiently primitive vectors.
- Dylan16807 6y ago> the function calls make it crystal clear that something non-trivial is happening under the hood Alright well I'd also rather write "vec1 [*] (vec2 [+] vec3)" too. Or equivalent.
- pharmakom 6y agoI don't understand this benefit. so in zig we are trusted to manage memory but we can't be trusted to overload operators in a sensible way?
- jorangreef 6y agoI believe the rationale against operator overloading is not a question of whether the programmer should or shouldn't be trusted, but rather simply about optimizing for code readability.
- pjmlp 6y agoThen better integrate some static analysis with natural grammar knowledge to ensure all functions are properly named for their actual purpose.
- deleted 6y ago[deleted]
- deleted 6y ago[deleted]
- deleted 6y ago[deleted]
- generichuman 6y agoThe benefit is that you don't have to be concerned about hidden function calls. You don't get unexpected function calls or performance hits this way. If you want your code to run fast, you need to ensure that you know what your program is doing and you're in control, at all times. Manual memory management enables that.
- pharmakom 6y ago... but operator overload is not really different to function overloading, it's just that the function name is infix rather than prefix.
- potta_coffee 6y agoI'm much more interested in Zig than Rust, but I'm also the kind of guy that is attracted to C over C++, and I also really like Go. I actually don't care about arguing the merits of one over the other, I'm just really happy and excited that there are cool new languages out there and it seems like here's really something for everyone.
- teleforce 6y agoNot really sure why the original HN poster left "D" from its title, is there any conspiracy going on to silence D :-) Actually unlike C++ and Rust you can have a lean D and most probably it can perform as good as Zig if not better. D has excellent CTFE support similar to Zig and D has no macro as well. If you want extra memory safety with ownership support D has that feature covered.
- MaxBarraclough 6y ago> unlike C++ and Rust you can have a lean D and most probably it can perform as good as Zig if not better I doubt this is really meaningful. The performance-ceiling on all four languages is presumably very high, as they all allow you to write hand-optimised low-level code if you want to.
- p0nce 6y agoPretty much, so you could as well use the one you find most productive. If productivity is not there, fast programs don't materialize.
- MaxBarraclough 6y agoYou could go for productivity, but that's not the only dimension. You could emphasise safety: C++ has no equivalent to Rust's safe subset. I believe D has a safe subset too, but I don't think Zig does. You could emphasise portability: C++ seems like the clear winner in terms of having the most supported platforms.
- pjmlp 6y agoMicrosoft and Google are trying to retrofit a safe subset into C++, via the Core Guidelines and lifetimes, enforced via static analysers. Since C++ is more relevant to what I do, naturally I keep validating how good they fair and so far not good. Lifetimes checker for example, is only able to deal with basic workflows and when it complains the error messages aren't that good, leaving you wonder why the analyser considered it an incorrect lifetime. Main reason being, if the call sites aren't annotated, it can only guess the lifetime use, and mostly it gets it wrong. No doubt if they keep throwing money at it, they might eventually solve it, however this is already a three year effort and this is the best so far.
- 7thaccount 6y agoNewcomers to systems coding need a really good intro guide as there are so many new things to learn (Ex: pointers, freeing memory). With high level languages, the syntax and garbage collector do everything for me. I build complex and nested data structures in Python daily and have no idea how to do the equivalent in C. Zig seems to assume you already know C, so I never see it winning the onboarding crowd. This is also why Clojure, Scala, F#, Rust...etc will struggle to get big.
- kristoff_it 6y agoThere's a lot to do, sure, but while we don't have yet swats of learning materials for complete newcomers, I consider Zig to be today the cheapest way of getting into "systems" programming for people that never did it before. I run Zig SHOWTIME [1] and we have quite a few talks that introduce basic concepts to people that aren't used to systems programming. Again, not yet enough to consider the problem solved, but it's a step in the right direction and we'll build a bigger library as more people join the community. [1] https://zig.show https://zig.show
- timClicks 6y agoThis problem was exactly why I wrote Rust in Action. It teaches you systems programming alongside teaching Rust. That said, you can get quite far without needing to understand pointers or memory management in any of the example languages you mentioned - "Clojure, Scala, F#, Rust". In the first 3, the garbage collector still does most of the work. In the last, there is are no calls to malloc() or free(). Rust's compile-time lifetime concept allows to compiler to do the work for you.
- 7thaccount 6y agoI should've been more specific. It's a different problem with Clojure, Scala, & F# where you're expected to understand the JVM & Java or the CLR & C#. It seems similar to Zig to me. There are numerous sources on learning C and I can barely figure out how to do a lot of things in Zig. Maybe a learn X in Y would help me.
- haberman 6y ago> Essentially, Zig being the "Go" of C/C++/Rust/D. I'm not sure that's a fair comparison, as Zig features compile-time evaluation ("comptime") that supports first-class types. Generics falls out of that by letting you create types imperatively at compile time. It's hard to imagine Go ever going in this direction. As a long-time C programmer, I am really excited to try out Zig once it stabilizes a bit more. Zig's take on generics seems like a compelling alternative to C++ templates.
- kristoff_it 6y agoIf anything, I'd argue that Rust is the "Go" of systems programming.
- slimsag 6y agoReally surprised to hear you say this, actually - in my mind Rust is the can of worms that allows almost anything and everything in terms of language features and will likely end up as bloated (if not more) than C++ in the next 20 years. What makes you argue that Rust is the "Go" of systems programming?
- kristoff_it 6y agoI wouldn't use this analogy as an everyday tool, but in reply to the comment above, if we really have to give the "Go" role to either Zig or Rust, I'd choose Rust for the main reason that, while you can certainly come up with a lot of abstruse abstractions, the language ultimately wants to keep you away from certain tools that would otherwise be considered core to a systems programming language. I'm of course referring to the restrictions that ensure safety at the expense of being able to implement some kinds of algorithms (in a "safe" way). Go in may ways has the same approach: stop developers from doing potentially damaging things (according to Go's definition of what's "potentially damaging"), which I guess is one of the reasons why the creators decided to remove pointer arithmetic and add a garbage collector. Zig wants to keep the language small, but the programmer is expected to have access to all kinds of advanced tools, in a way that I don't see neither Go nor Rust consider acceptable. In Zig you can use `undefined` to avoid initializing memory, do all kinds of pointer arithmetic, and while you can't overload operators, you can use comptime to concoct all kinds of static/dynamic dispatch schemes, the only limit is just that they can't masquerade as basic operations. Of course you can do all these things in Rust unsafe, and same in Go with cgo, but the point is not what you can or can not do, it's what the language guides you towards, and the only potentially damaging thing that Zig tries to prevent you from doing, is making code hard to understand for readers.
- pjmlp 6y ago50 years of C and a few less of Objective-C and C++, have proven that C.A.Hoare was right regarding language defaults in computer security, in the approach taken by ALGOL based systems programming languages, starting with ESPOL/NEWP. If I want unsafe languages, C and C++ already fulfil the purpose with 50 years of ecosystem. Being safer than UNIX for certain kinds of customers, is one of the selling points of Unysis ClearPath MCP, thanks to NEWP.
- kindofajsdev 6y agoWhy is no metaprogramming a good thing? I'm mostly a web dev but I've played with crystal quite a bit. Crystal embraces macros & metaprogramming. Its almost a language within the language. Furthermore, isn't go a statically compiled language? Why isn't go comparable to c + others?
- jonahx 6y agoAs a ruby developer who went through a stage of being enamored with meta-programming, I'd argue that it's siren song: enticing, but dangerous and destructive. It's especially dangerous because it's fun to write -- it scratches the "I'm so clever" itch -- but is a disaster to read, and a disaster to maintain. Aside from just wanting to be creative, the desire to do it usually indicates either a missing core language feature, or a programmer's missing knowledge of how to use core features effectively and idiomatically. Otherwise you'd use the simple core language feature (the one every developer is guaranteed to know how to use already) to achieve your goal. The other reason it's sometimes used is prioritizing terseness over simplicity. That is, you introduce some meta-programming abstraction that saves you some LOC, but at the cost of abstraction overhead. Almost always, you're better off with the reverse priorities.
- iFreilicht 6y agoBecause what zig does is much more intuitive: it has a comptime keyword that allows you to execute almost any function at compile time. So you use the same language for programming and metaprogramming, there’s no weird second syntax to learn. Even better, they also use that approach for generics, where types are just comptime parameters. Again, makes the syntax more consistent and requires less different ways of thinking.
- lightgreen 6y agoThe largest difference between Zig and Rust is Zig does not have memory safety. This arguments is more important than all other differences combined. Because when writing very large long living projects, memory safety is the most important issue.
- jorangreef 6y agoZig does however go a long way towards memory safety, to be fair, and if you wanted to, you could also argue similarly that Rust does not guarantee 100% OOM safety whereas Zig does: https://www.youtube.com/watch?v=Z4oYSByyRak https://www.youtube.com/watch?v=Z4oYSByyRak I would also say that for "very large long lived projects", memory safety is not actually the most important issue, but rather correctness, followed by (in no particular order) safety, performance, explicitness, readability and exhaustive fine-grained error handling, the latter also not found in too many languages besides Zig.
- alserio 6y agoIsn't memory safety a subset (and I would argue a necessary requisite) of correctness?
- jorangreef 6y agoGood question. Assuming we both mean "memory safety" as in a guarantee given by the language (e.g. Rust), then no, logically speaking, it can't be a requisite for correctness, and it's not even a subset of correctness. Here's why: If you can write a correct program in a language which does not guarantee memory safety (which we certainly could, for example, simply by not allocating memory at all, or not using pointers etc, or by using runtime checks e.g. to ensure there are no double frees or out of bounds reads/writes), then memory safety is neither a subset of, nor a requisite for correctness. Memory safety is a double-edged sword. It can make correctness easier to achieve. But that also depends on how the language implements memory safety. If this is done by at the expense of a steeper learning curve, then that could in itself be an argument that the language is less likely to lead towards correctness, as opposed to say an almost memory safe language that implements 80% of this guarantee while optimizing for readability, and with a weekend learning curve. Historically, the lack of memory safety has obviously been the cause of too many CVEs. But even CVEs in themselves are more a measure of security than correctness. I would say that exhaustive fine-grained error handling checked by the compiler is probably right up there for writing correct programs.