I do think that these sorts of language comparisons are useful, but they don't always generalize. Partially this is because what a language means to each person can vary. As long as they're understood in a very coarse grained way, I think they can still make sense, but it's tricky!
I'm one of those people, and, while I have yet to use Rust or Zig for any real work, at least so far I also see Rust as being more directly a competitor to C++, and Zig as the more direct competitor to C.
Or perhaps I should say analogue. Because, it's true, I might choose Rust over C. I'm even tentatively planning to, for one project that's still in the idea phase, and that I would normally have wanted to do in C. Though that's not really because I see Rust as being more C-like. It's more that I see choosing Rust as being perhaps more likely to be worth the extra effort than I've found to be the case for C++.
I've seen this play out a number of times. "Rust cannot compete with C, because C is too entrenched in embedded." "I do embedded development in Rust, so it can." "Well, I work with these chips that Rust can't target yet, so it can't, for me." None of these statements are incorrect, but they can lead to huge back-and-forths.
Words are hard.
I did fairly large and complex program in Rust for bicycle computer, and, while I like developer ergonomic, speed, and memory usage, I'm disappointed by the total size of the binary, number of dependencies used, and compilation time.
Have you researched this space already[1]? By default Rust doesn't optimize for the resulting binary size, but there are lots of things that can be done to bring size down where you'd expect.
> number of dependencies used
When this comes up it becomes as much a technical discussion as a philosophical one :)
> and compilation time.
No arguments there. There are some things that can be done in your project to avoid spending too much time (simplify bounds to minimize recalculation in the type system, avoid proc macros, leverage cfg conditional compilation), but they are work arounds.
Could you elaborate on this?
I'm a C programmer. I haven't touched C++ since before C++11 became a thing, and I prefer C to C++. I'm much more excited by Rust.
Yes, you can turn many of these things off or ignore them and just use D as a better C, but the same could be said of C++ (for some definition of "better"). Or most languages, really, if you squint enough.
I use D as better C. I use it as better C++ at times. Once ownership/borrowing [1] is stable, I'll use it as a better Rust too.
For close-to-the-metal code, I use D's inline assembler [2]. "To go any lower level than that, you'd need a miniature soldering iron and a very, very steady hand." (Andrei Alexandrescu)
[1] https://dlang.org/blog/2019/07/15/ownership-and-borrowing-in...
"One of the earliest design decisions that Walter made about D was that it would be easy to use with software written in C. Many widely-used libraries are implemented in C or have a C interface. He wanted to provide an easy path for established software companies to adopt the D language. A straightforward approach was to guarantee that users of D could immediately take advantage of any C library their project required without the need to reimplement it in D." from "Origins of the D programming language" by W.Bright, A.Alexandrescu, M.Parker.
D has an optional conservative mark-sweep Boehm GC which only gets triggered when you attempt to allocate something on the heap. If you don't do that, it won't bother you. Finally, you can explicitly disable it.
People generally ignore or miss the fact that D really shines when it comes CTFE, reflection and metaprogramming.
You still get the following language features, which is more than what C, C++ or Rust have to offer (though Rust is hot on D's trail).
Unrestricted use of compile-time features
Full metaprogramming facilities
Nested functions, nested structs, delegates and lambdas
Member functions, constructors, destructors, operating overloading, etc.
The full module system
Array slicing, and array bounds checking
RAII (yes, it can work without exceptions)
scope(exit)
Memory safety protections
Interfacing with C++
COM classes and C++ classes
assert failures are directed to the C runtime library
switch with strings
final switch
unittest
printf format validation
https://dlang.org/spec/betterc.htmlMemory management is one of the more important aspects of systems programming so it is useful to be clear about what is the main strategy used by each language.
Just please do not make the mistake of believing that it is unique to Zig. Factor brings the best of Forth and Lisp together, so meta-programming or extending the language is possible quite easily, for example. You could extend the syntax or add constructs pretty easily, and so forth. Anyways, an example can be found here: https://rosettacode.org/wiki/Compile-time_calculation#Factor but this barely scratches the surface. It does not mention `<< ... >>` which evaluates some code at parse time. You can execute code before the words in a source file are compiled.
https://docs.factorcode.org/content/article-literals.html
https://docs.factorcode.org/content/article-syntax-literals....
https://docs.factorcode.org/content/article-syntax-immediate...
https://docs.factorcode.org/content/word-flags{,literals.htm...
I remember when I did something like:
SYMBOL: aligned-16-char
<<
: 16-byte-alignment ( c-type -- c-type )
16 >>align 16 >>align-first ;
char lookup-c-type clone 16-byte-alignment \ aligned-16-char typedef
>>
when I was working on some binding.You could use it in a struct like:
STRUCT: foo
{ bar aligned-16-char[16] } ;
Or something like this is pretty typical (when writing bindings/ffi): << "libotr" {
{ [ os windows? ] [ "libotr.dll" ] }
{ [ os macosx? ] [ "libotr.dylib" ] }
{ [ os unix? ] [ "libotr.so" ] }
} cond cdecl add-library >>
Those are just some examples, but it is pretty powerful. It supports (and encourages) interactive development. Profiling and debugging is a breeze and highly detailed and useful, you can easily disassemble words (functions), you can get a list of how many times malloc has been called in some circumstances, there is runtime code reloading (a vocabulary that implements automatic reloading of changed source files[1]), and so on. And on top of all this, you can compile your stuff to an executable that is less than 4 MB!And of course you do not have to do stack shuffling at all, you can easily use locals which is useful for math equations and whatnot. Plus did you know that the Factor compiler supports advanced compiler optimizations that take advantage of the type information it can glean from source code? The typed vocabulary (yes, it is not part of the language, but implemented as a vocab) provides syntax that allows words to provide checked type information about their inputs and outputs and improve the performance of compiled code.
I would like to repeat because if this was not the case, I would have never bothered with it: you can create a single executable file that is less than 4 MB of size if you wish so! Of course it encourages interactive development, but still, it is great to have an optimizing compiler that can do all this easily. And mind you, this part is also written in Factor itself and is available as a vocabulary (vocab).
[1] There is a vocabulary named io.monitors and loaded source files across all vocabulary roots are monitored for changes. You can read more about it here: https://docs.factorcode.org/content/article-vocabs.refresh.h...
---
So all in all, I think Factor is great. I was shocked at how modern (and how many) libraries it has, especially considering only a handful of people have been working on it. Slava Pestov created the language, and some people joined him later on. If you want to learn more about it, start here: https://concatenative.org/wiki/view/Factor. There are videos, there are papers, there are lots of resources to get started. :) The language misses a couple of things, but it is being worked on.
What is unique to Zig is that it has these features without bringing together "the best of Forth and Lisp". Sometimes, just being pedestrian is a virtue.
EDIT: In fairness, Zig's presentation is pretty likeable. You can do a comptime expression pretty trivially in LISP, a comptime parameter or block would require actual effort.
https://ziglang.org/#Order-independent-top-level-declaration...
Also if comptime is simply compile time evaluation / execution, C++ has it with constexpr and templates.
Nim has this too I think.
Though judging by your comment not as powerful as Factor (I'm not familiar with it) extending syntax etc.
circle C++ probably gets closer: https://www.circle-lang.org/
There's also the downside that transpiling to C locks you into an ABI, which limits what compiler developers can do towards the end of the compilation passes.
Zig will eventually have this feature. It is a work-in-progress: https://github.com/ziglang/zig/blob/master/src/codegen/c.zig
It's tempting to view `comptime` as additional complexity, but the truth is that it replaces a much more complicated and hard-to-work-with system. That one is just one that people have gotten used to over decades.