IMO describing Rust as a "C replacement" is slightly off the mark because Rust's value proposition is very different from C. Rust is about giving you the best possible performance in a safe-by-default language. C is about giving you maximal control over memory, with a very thin layer of abstraction over the hardware.
C has traditionally been used in a lot of places where Rust's USPs add a lot of value -- for instance in kernel development -- simply because there wasn't a safe alternative. However I think there are other cases where the strengths of C still add value; for instance in game development you're largely trying to do high-throughput processing over large swaths of structured data, and Rust concepts which help safety like RAII just get in your way. Yes there are ways to get around this in Rust, but Rust is not really optimized for structured, manual memory management.
As a result I think there is plenty of room for something like C which just has better ergonomics and some more modern features.
In D, the ""borrow checker"" is being tacked on as an after thought, in an attempt to copy Rust. This means that it doesn't play nice with existing features and makes it difficult if not impossible to guarantee that memory isn't leaked or used after it was free'd. For example with exceptions. The checker doesn't check for exceptions and if memory is free'd correctly if an exception is thrown. This isn't a problem in Rust because it doesn't have exceptions so it doesn't have to worry about checking them so it can maintain its strong guarantee.
Rust does have something similar to exceptions when compiled with "panic=unwind" (the default). It uses the same mechanism as C++ exceptions to unwind the stack (while calling all the necessary destructors), can be caught (std::panic::catch_unwind) and rethrown (std::panic::resume_unwind), and has some of the same concerns as C++ about "exception safety" (mostly within unsafe code - the programmer has to take care to leave the objects in a safe state when it can unwind).
"only one mutable reference to a mutable object OR many const references to an object"
and that's where the similarity to Rust begins and ends. The realization of that principle is quite different.
There's really no point discussing this with you any further. You don't contribute anything to the discussion. That's just PR babble, and provides zero information and doesn't even deny the fact that exceptions break memory safety with D's borrow checker (you know it that's why you don't deny it like a "good" PR person).
So as long as UNIX clones exist C will be around. OSes with POSIX APIs is a different matter, because there the kernel can be something totally different, e.g. mainframes.
So for that, we still need something like Checked C.
Or as Oracle, ARM, Google are doing, hardware memory tagging.
So, yeah, really not a C replacement at all imo
But this is all a bit uglier at the call site, since either the library provides value-semantically-similar things with different names that either eat their arguments or copy-from-reference them, provides only the argument-eating version and relies on the caller to `clone` at their discretion, or provides only the referencing version and fails to elide copies.
In C++ you can provide a referencing version, and an argument-eating version with the same name that is called automatically when the user gives it a temporary or specifically requests it via `std::move`. Automatically eating temporaries is very nice in the case where the caller would like to compose a bunch of "create-new-from-a-set-of-references" operations in a single expression to create one new thing from an initial set of references.
The canonical example is eliding copies in stuff like
B*(A*x + b) + c
with overloaded * and + for vectors, since you really don't want to use something with a name other than + to request copy-elision. If you are one of those people who is grumpy about operator-overloading, you can imagine doing this with other "copy-some-refs-create-something" type functions, ... but actually you are probably grumpy about overloading those too. fn eats_a_string(s: impl Into<String>) {
dbg!(s.into());
}
fn main() {
eats_a_string("foo");
eats_a_string("foo".to_string());
eats_a_string(&"foo".to_string());
}
In that case, eats_a_string() will allocate internally in the first and third cases, but it won't allocate in the second case, because the caller gives up ownership of its own allocated String. I wouldn't say this is a super common idiom, and "provide only the argument-eating version and rely on the caller to `clone` at their discretion" is often preferred to be simpler and more explicit. But you see it occasionally in the standard library, for example here: https://doc.rust-lang.org/std/ffi/struct.CString.html#method...I would enjoy reading a couple paragraphs, or a blog entry about this.
When I see someone saying that they prefer D/Nim/Zig value proposition as a "better C" than Rust, what I see is that people are just starting to realize that there are many more options than C for doing programming tasks that require a more precise interaction with the hardware.
It's just not "assembly", but there are multiple different assemblers with different trade-offs. It's also not "assembler < C" anymore, since in some aspects newer languages do offer more low level control about certain things than C.
It's also not just C/C++/Zig/Rust/Nim/D competing at the lowest-level of the space. Ada, Forth, Scheme, and many other languages are also widely used in this space. They just aren't the "next new thing".
So only the old generation remembers how things used to be, and those that are curious about the computing history and evolution of programming languages.
That world is dominated by C++ for the most part, though. So D's -betterC would be fighting against an incumbent that is itself also already a "better C".
What value does D's -betterC bring to the table here? Skimming through it, it doesn't look like there's any compelling reason to switch to -betterC from an existing C++ code & knowledge base.
I think what a lot of people would want is something which maintains that simplicity, but mainly delivers on basic QOL lessons we've learned in the past 40 years, like that it's nice not to have to pass around array lengths as separate variables.
Sure, but D has those OO features, too, so that's an argument against both, not one.
> like that it's nice not to have to pass around array lengths as separate variables.
-betterC doesn't have dynamic arrays, though. That's one of the "unavailable features" currently.
But that's also sommething C++ solved as well - std::vector does exist, after all, as does move semantics to avoid copying it unnecessarily.
C++ is far from perfect, of course, but D's -betterC doesn't really fix any of C++'s issues and it doesn't retain C's simplicity. It's in an awkward middle ground between the two - is that really a viable place to be? For users that already know & have C++ codebases, what's the sales pitch here? For existing C users, what does -betterC do that hasn't already been done and already failed to attract the C holdouts?
You can get them in C++, and VC++ does it by default on debug code, but not all compilers do by default and it isn't required by the standard unless you explicitly make use of the at() variants.
On the other hand, every attempt to bring bounds checking to C has failed, lets see how far Microsoft goes with Checked C.
In particular, I really love that I seldom use operator new. Objects are instantiated in the stack and object creation is ridiculously fast.
True, but the trouble with simplicity is a number of things become excessively tedious and error-prone to code - such as ensuring no buffer overflows.
C makes up for its lack of expressivity by adding a text preprocessor. The preprocessor is a tacit admission that the core language simply isn't powerful enough. When people find themselves doing metaprogramming with the C preprocessor, it's really time to graduate to a more powerful language. DasBetterC doesn't need a preprocessor, and its metaprogramming facilities far outstrip the C preprocessor.
...and are severely crippled by applying betterC constraints to CTFE:
// CTFE-only
string genint(string name) {
return "int " ~ name ~ ";";
}
void main() {
mixin(genint("x"));
}
Error: array concatenation of expression "int " ~ name ~ ";" requires the GC which is not available with -betterCWhich suggests an avenue I haven't seen anyone take yet: Pick a subset of features common to sane hardware and write a language which gives access to those features in a way which is reasonably portable and as explicit as C.
Pick a spot midway between a macro assembler and a language with a complicated optimizer and give as much access to the hardware as possible while not wedding yourself to a single ISA. Make cross-compilation a first-class feature using a module system, and error out if the programmer tries to use SIMD intrinsics when compiling for an MSP430 or something.
It basically follows on the school of thought at Xerox PARC, ETHZ, and Microsoft Research.
Now what it lacks is more more manpower to improve its runtime capabilities and having a big name actually pushing it forward.
However this doesn't need to be a zero sum game, any language that helps to fix C is welcomed to the party, including attempts like Checked C and Frama-C.
Yes, betterC does not use the GC, but I was speaking about the whole language, and many anti-GC folks eventually discover that they can stick to regular D and still deliver what they were trying to do.
You can imagine an OS written in D, where BetterC mode gets used in the layers that for whatever reason cannot afford a GC, while all the remaning layers can happily take advantage of it.
https://www.twitch.tv/videos/602715503 [edit: at 10:20]
From the reference I found[1], Andrew's stance is: The current zig tooling forces spaces instead of tabs because the tooling for the language is not yet complete. The self hosted compiler (which the community will start using just as soon as it's ready) already does accept both tabs and spaces.
So, I'm not able to understand your complaint at all. Perhaps you could you elaborate, provide a reference, or soften your stance?
[1]: https://github.com/ziglang/zig/wiki/FAQ#why-does-zig-force-m...