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))".
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.
Alright well I'd also rather write "vec1 [*] (vec2 [+] vec3)" too. Or equivalent.
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.
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.
Google has gotten much better at handling symbols.
Basically, custom infix operators are super convenient as long as they are auditable and aren't abused.
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.
To someone just skimming the code though, it might not at all be obvious that “+=“ and “/=“ both exist as concatenation operators, and their behaviors are different.
But yes it’s an aesthetic preference or opinion; I tend to prefer operators only when their only possible behavior is nearly so obvious that no documentation should be necessary. When that’s not the case, I prefer named functions/methods because they permit being explicit about subtle differences.
OTOH / is an instant mnemonic cue for "append with separator", and + matches the + on std::string.
In fact, to generalize, I think we can fairly say that operator overloads are akin to extremely short function names; in some cases they may work out great when there is not much ambiguity implicit in the underlying problem, but in other cases a longer and more explicitly descriptive name is required to disambiguate. In this case, “append” and “concat” seem quite poor names for different functions, given that they are virtually synonymous and therefore do nothing to describe or distinguish their differences of behavior.
So my claim is that we can do significantly better at resolving ambiguity with carefully named functions (or other approaches) than operators, or tersely/ambiguously named functions (like “append” and “concat”). Of course, this does come at the cost of code verbosity. Just where we should draw the line between too ambiguous vs too verbose, is of course a difficult subjective matter.
I mean, most of us aren't going use linear algebra, but still...
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.
2) Slightly more real-world: Measuring things when you only have approximate rulers, have difficult things to measure (odd surfaces, etc.), or have to calculate from 2nd hand measurements (pictures with rulers, etc.)
Not sure if that qualifies as Real World, but...?
Weird to think that game programming is (weirdly) kind of a niche thing at this point. Compared to programming boring CRUD apps, that is. :)
Lovely.
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.
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.
C#: foo[index] = bar[index];
Java: foo.put(index, bar.get(index));
Sometimes there are good reasons to mask complexity.
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.
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.
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.
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
Those two factors make me think it's close to the ideal language for newcomers to lower-level programming. Even the syntax ain't bad, as far as C-like syntax goes; it'd be interesting to see some alternative languages that put alternate syntaxes over Zig's semantics, though (and one of my New Year's Resolutions is to put together something to that effect, whether by transpiling to Zig or by (ab)using Zig's compile-time programming).
Zig is insanely easy to read coming from high level languages like these. It's only when you start to write Zig that you need to take more time to understand, for example, the difference between the lifetime of a pointer to stack allocated memory vs a pointer to heap allocated memory.
But by then, I think Zig will have already hooked you.
Zig is also a great way to learn systems programming, because the design of the language and the std lib code itself is such a great teacher.
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.
Many of us were doing systems programming with other languages before C went mainstream.
What you need to learn is computer architecture.
Getting back to JVM or .NET, you can get hold of JIT Watch, VS debug mode or play online in SharpLab.
Get to understand how some code gets translated into MSIL/JVM, and how those bytecodes end up being converted into machine code.
https://github.com/AdoptOpenJDK/jitwatch/wiki/Screenshots
Languages like F# and C# allow you to leave the high level comfort and also do most of the stuff you would be doing in C.
Or just pick D, which provides the same comfort and goes even further in low level capabilities.
Use them to write a toy compiler, userspace driver, talking to GPIO pins in a PI, manipulating B-Tree data stuctures directly from inodes, a TCP/IP userspace driver.
Not advocating not to learn Zig, do it still, the more languages one learns the better.
Only advocating what might be an easier transition path into learning about systems programming concepts.
With Python, Perl,R, Bash... whatever, I never had to know any C to be very productive.
With F#, good luck not knowing C#. With Clojure, not knowing Java is a pain. I've tried both several times and was frustrated by constantly being referred to something Java related that I didn't understand. Because of that, I feel like those languages will never pick up anyone outside of regular users of C# or Java that want to try something different. Of course there are exceptions, but they're just that.
I agree with you that perhaps one should go through all of what you're referring to Ex: learn assembly, write a Forth...etc.
I'm just saying a lot of these supposedly amazing languages stagnant with regards to their userbase for a reason. APL uses an entirely different way of doing things than Clojure, but I'd bet I could get up to speed a lot faster with APL than Clojure where I constantly have to fight with learning the daunting ecosystem.
Furthermore, isn't go a statically compiled language? Why isn't go comparable to c + others?
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.
What makes you argue that Rust is the "Go" of systems programming?
At least at the moment, Rust's many features are mostly orthogonal and work well in combination. But in some sense, that makes the learning curve even harder, because you can't focus on an "accepted subset" as you would in an existing C++ codebase.
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.
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.
We will keep the productivity of the existing eco-systems and tooling, with an additional tool for the 1% of use cases that it actually matters.
Languages like Rust and Zig are just on a different level, for cases where performance or hardware restrictions are the number one concern.
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.
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.
For a systems programming language though, I think Zig hits the sweet spot, and not only with regards to memory safety.
Correctness, in this realm, is as much memory safety as:
* error safety (making sure that your program correctly handles all system call errors that could possibly occur, without forgetting any, and there are many! The Zig compiler can actually inspect and check this for you, something not many languages do), see https://www.eecg.utoronto.ca/~yuan/papers/failure_analysis_o... for how critical error handling is in distributed systems,
* OOM safety (making sure your program can actually handle resource allocation failures without crashing),
* explicitness (clear control flow with a minimum of abstractions to make it easy to reason about the code with no hidden surprises, no weird undefined behavior),
* and especially as much runtime safety as you can possibly get from the language when you need to write unsafe code (which you will still need to do when writing systems code, even if your language offers memory safety guarantees, see https://andrewkelley.me/post/unsafe-zig-safer-than-unsafe-ru...). Here, Zig helps you not only at compile time, but also at runtime (and with varying degrees of granularity as you see fit), something not all systems languages will do.
On all these axes, Zig is at least an order of magnitude more likely to lead to a correct program than C, while optimizing for more readable code (even for someone coming from TypeScript) and thus code review, also essential for improving the odds that your code is correct with respect to its requirements.
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.
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.
[1]https://dlang.org/blog/2017/08/23/d-as-a-better-c/
[2]https://dlang.org/blog/2017/08/01/a-dub-case-study-compiling...
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.
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.