
Better C – A subset of D Programming Language - dgellow
https://dlang.org/spec/betterc.html
======
F-0X
One thing that really annoyed me about D is that its documentation lists
basically every function with an 'auto' return type. Auto should be completely
banned from documentation, it's a complete hindrance. It's the single biggest
contributor to why I stopped using D for personal projects - I was so tired of
having to hunt down what functions are going to return, sometimes having to
resort to just using it and looking at what the compiler complains about.

And that's a huge shame. Because in general I really liked using D.

~~~
lumberjackstian
I've felt this as well, been using D for a couple years now, and this is the
kind of thing that just makes me have to context switch more than I'd like.
With the current implementation of the language it's hard to avoid, and
function's return type can be quite complex, so writing it down can be hard.

Another reason is the (ironically) dynamic nature of a return type. E.g.

auto whatDoesItReturn(int i)() { static if (i == 0) { return int.init; } else
{ return string.init; } }

Template code can do that quite easily and then you don't have a choice but to
write auto as the return value.

What would be fantastic if the documentation could be given access to the the
compiler's return type inference, so that it could document the auto returns
with a little more info.

Another way useful approach would be to implement protocols like in swift, or
traits like in scala/rust/others, signatures in ml, etc. Then you would be
able to define the interface of what a function returns.

~~~
GordonS
I haven't looked at D yet, but... _yuck_! `var` for return types in a method
signature is seriously unhelpful!

If the docs are filled with this, then D is certainly coming off my list of
langs to look at.

For functions that can return different types, I think interfaces or union
types would be more helpful (not sure if D supports either though).

~~~
schveiguy
Yes, it supports interfaces and unions. One thing not being stated enough in
this thread also is that the docs are not just a regurgitated version of the
prototypes -- there's actual hand-written text that tells you what the things
return.

------
dgellow
For more context and details, Walter Bright wrote a series of blog article
between 2017 and 2018 on the subject (though their content is a bit outdated
as more D features are now supported in BetterC mode):

\- "D as a Better C" (2017): [https://dlang.org/blog/2017/08/23/d-as-a-
better-c/](https://dlang.org/blog/2017/08/23/d-as-a-better-c/)

\- "Vanquish Forever These Bugs That Blasted Your Kingdom" (2018):
[https://dlang.org/blog/2018/02/07/vanquish-forever-these-
bug...](https://dlang.org/blog/2018/02/07/vanquish-forever-these-bugs-that-
blasted-your-kingdom/)

\- "DasBetterC: Converting make.c to D" (2018):
[https://dlang.org/blog/2018/06/11/dasbetterc-converting-
make...](https://dlang.org/blog/2018/06/11/dasbetterc-converting-make-c-to-d/)

------
kqr
This is a great idea. I maintain that Ada is a better "better C" than any of
the alternatives I've looked into, but it has an obvious big hurdle: while it
has approximately the same use cases as C, it is completely different in terms
of looks and handling. One of the strong points of D is that it still seems
very much like C. Very good call to emphasise this.

~~~
elcritch
I’ve always liked the idea of Ada, but never had a place to use it. Though
someone on HN pointed out that NVidia was using Spark for secure code
sections. Quite interesting!

Personally while D seems a great tool, I really keep running into situations
where a language that lives on top of C/C++ is useful. So I’ve been trying out
Nim for those use case, using the ARC GC which appears to work well for
embedded. It’s deterministic but with move semantics for performance
optimization. Interesting approach IMHO. But the biggest advantage is being
able to directly interface with any C or C++ natively. D/Rust both seem to
have difficulty being 100% onboard with C++ (for good reasons).

~~~
e12e
> I really keep running into situations where a language that lives on top of
> C/C++ is useful. > the biggest advantage is being able to directly interface
> with any C or C++ natively. D/Rust both seem to have difficulty being 100%
> onboard with C++

I thought one of the centerpieces of D was seamless c++ interop? Where does it
fall down?

~~~
VHRanger
You just need to do a bit of tiresome explicit declarations to statically link
the code.

For libraries with a large API that can be an annoyance.

~~~
elcritch
AFAICT, D requires the C++ types be exported, included any predefined template
objects or functions. E.g. you can’t dynamically wrap `std::map<K,V>`, unless
it’s already been instantiated in C++ library you’re linking. The same in Rust
as well. See D Lang manual:

    
    
        Note that all instantiations used in D code must be provided by linking to C++ object code or shared libraries containing the instantiations. 
    

Whereas Nim is one of the few languageS that can dynamically wrap C++ template
types into its type system. Maybe Zig can do it too? See Nim manual [2].

    
    
        type StdMap {.importcpp: "std::map", header: "<map>".}[K, V] = object proc
        ...
        var x: StdMap[cint, cdouble]
        x[6] = 91.4
        ...
        std::map<int, double> x;
        x[6] = 91.4;
    

This makes it nice to wrap C++ libraries. :-)

1: [https://dlang.org/spec/cpp_interface.html#cpp-
templates](https://dlang.org/spec/cpp_interface.html#cpp-templates) 2:
[https://nim-lang.org/docs/manual.html#importcpp-pragma-impor...](https://nim-
lang.org/docs/manual.html#importcpp-pragma-importcpp-for-objects)

------
optymizer
Mr. Walter Bright, I'm late to the party here, but I hope you will see my
comment.

I have a lot of respect for you and Andrei, and I think D deserves much more
love than it gets.

I personally feel like there are too many options when it comes to D. Perhaps
it would be good to double down on some combination of those options and make
that widely known?

As a newcomer to D, I am exposed to GC/non-GC code, dmd/llvm compiler/gcc
compiler, several debuggers, some BetterC option, etc. I don't know which
toolchain is best, and I don't want to deal with the integration issues
between them.

All of these options that exist are great, but the brand gets diluted. D seems
to be the ideal thing: it's seemingly a better C++, and a better C, with a
clean syntax like Java.

I trust it's a better C, but how do I know the combination of the D toolchain
that I'm choosing is better than sticking with the devil I know already?

What I would love to see is an opinionated package spun off from D. One
compiler, one debugger, one IDE, one standard library. Make my onboarding
experience better and take the thinking out of assembling a D toolchain.

Every time I find myself thinking "maybe I should look into using D instead of
C for this project", I spend a few hours with D, and then I get frustrated at
all the options and I go back to the devil I know.

I truly wish I could be one day convinced that I can install D and I get a
solid platform without weird moving parts.

~~~
dgellow
Yes, I completely agree, the onboarding experience is daunting. It's quite
hard to understand all the moving parts and figure out which one I should care
about or not.

------
sgt
D is pretty exciting. Seems like a perfect choice for those of us looking for
an alternative systems programming language and are not completely convinced
we'll be happy in Rust.

~~~
skohan
Yeah I think "better C" is a space which has a really good reason to exist and
I'm always interested to see new entrants.

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.

~~~
WalterBright
D also now supports Ownership/Borrowing (experimental) on a per-function
basis, meaning it can be added incrementally to an existing program.

~~~
exiled13
D's borrow checker fails in comparison to Rust's. It serves very little
purpose and provides almost no guarantees. These issues have been brought up
multiple times but they are brushed aside with ignorant responses like "I've
been told my entire career what I'm doing will fail but I continue to do it
anyways".

~~~
WalterBright
Yes, you do create accounts to follow D around and complain about it.

------
arunc
For me the single best feature that stands out in D is the uniform member
access using dot.

\- Want to access member in a aggregate (class/struct)? Use .

\- Want to access member in pointer to an aggregate? Use .

\- Want to access member in a module? Use .

\- Want to access reference (not free standing)? Use .

This makes switching between different implementations pretty easy. Coupled
with UFCS, this is pretty fantastic!

~~~
jane_red
I can strongly relate to that. You can write a custom "fill" method for your
array and just do "arrray.fill", bam! It just works. You want numpy "zeros"?

Just do a template

void zeros(T)(ref T arr) { arr.each!"a = 0"; }

someArr.zeros;

------
WalterBright
Walter here - AMA!

~~~
yvdriess
A compiler related question:

A often recurring C idiom is passing a pointer to a struct as function
argument where passing the plain struct by copy would do. A big reason is the
good old 'passing by pointer is faster', which I thought to be no longer
relevant with modern optimizing compilers. Of course I found out the hard way
that even on modern compilers object copies are not elided on call. I had
performance sensitive parts of my (non-x86) code that were dominated by the
compiler's builtin memcpy, due to my struct-happy coding style (e.g. rolling
my own range struct and passing it by value everywhere).

I understand mostly why eliding argument copy is so much harder than eliding a
return object copy, there are so many ways to observe its effect, and you have
to obey the calling convention. Another aspect is the lack of programmer-
communicated immutability in C, which you have addressed with D. Does the D
compiler help in this situation? Can it guarantee that (immutable) argument
copies will be elided in certain circumstances? (e.g. in file-scope static
functions)

~~~
dfawcus
The proposed NUBI ABI[1] for MIPS was interesting here in that for call by
value if a struct was register sized, it would be passed in a register, but if
larger it would be implicitly passed by reference. See section 3.4, Arguments.

It was then up to the callee to make a copy if necessary, say if it modified
the struct contents.

Hence it would have been possible to elide the copies, on a per function
basis, depending upon how the function used the structure.

[1] ftp://ftp.linux-mips.org//pub/linux/mips/doc/NUBI/MD00438-2C-NUBIDESC-
SPC-00.20.pdf

~~~
yvdriess
Thanks for the interesting pointer.

I was also thinking in the direction of the compiler transforming the call-by-
value struct object argument into a call-by-ref one at specific call sites.
e.g. when the object is clearly on the caller's stack, is not mutated by the
function and the function is not taking its address.

As Walter pointed out, you can use refs in BetterC (and of course C++)
directly, but I don't see why it cannot be automatically applied to C in
general.

------
dmux
Not included as part of the subset linked above, but D's Contract Programming
piqued my interest:
[https://dlang.org/spec/contracts.html](https://dlang.org/spec/contracts.html)

~~~
thesuperbigfrog
Ada ([https://learn.adacore.com/courses/intro-to-
ada/chapters/cont...](https://learn.adacore.com/courses/intro-to-
ada/chapters/contracts.html)) and Eiffel
([https://www.eiffel.com/values/design-by-
contract/introductio...](https://www.eiffel.com/values/design-by-
contract/introduction/)) also support contracts.

Contract-based programming is a very nice way to quickly find errors and
specify how different parts of the program should interact.

~~~
pjmlp
C++20 almost got them.

There are some Java annotation processors that rewrite the bytecode for
contracts.

As extra info.

~~~
thesuperbigfrog
Java annotations have always struck me as interesting since they fill the Lisp
macros and C preprocessor niche for Java.

It's another layer of complexity, but it extends the language to offer a bit
more richness or layers of abstraction.

I would not be surprised if a future C++ standard adds contracts officially.
Boost seems to already have contracts:
[https://www.boost.org/doc/libs/1_67_0/libs/contract/doc/html...](https://www.boost.org/doc/libs/1_67_0/libs/contract/doc/html/boost_contract/contract_programming_overview.html)

~~~
pjmlp
Yes, many aren't aware how much annotation processors and compiler plugins are
capable of.

On the .NET side you have Roslyn, Attributes, expression trees and on F#
quotations.

While they are all a bit cumbersome to use versus what Lisp macros allow for,
they already allow for quite a lot.

Yes, there is some hope that C++23 will bring them.

The more languages support DbC the better, only so can we start seeing more
widespread adoption across the industry.

------
pjmlp
Now with WebAssembly support thanks to LDC, the Better C experience is
definitely much more productive than dealing with emscripten.

Then again, I am biased.

------
colonwqbang
The chosen example (printf) doesn't make D look very good compared to C. If I
modify the example to include a simple type error:

    
    
        printf("Hello %s", 123);
    

Then the D version will still happily compile without warnings and will
segfault.

Compiling with GCC -Wall -Werror, the type error is easily caught by the
compiler.

~~~
arunc
Use dmd master and you will have printf/scanf format argument validation [0].

[0]
[https://github.com/dlang/dmd/pull/10852](https://github.com/dlang/dmd/pull/10852)

~~~
WalterBright
Yeah, I am glad we fixed that one.

------
zem
every time I've looked into this I've found the documentation severely lacking
in examples of how one would go about incrementally migrating a c project to
better c. does someone have a pointer to a blog post or even a git commit that
illustrates the first step of migrating a single c file, with all the
attendant makefile changes?

~~~
WalterBright
1\. rename the file from prog.c to prog.d

2\. set up to compile with dmc prog.c -c -betterC

3\. replace all the preprocessor stuff

4\. compile it, and fix the errors diagnosed by the compiler

The hardest part will be how much metaprogramming was done using the C
preprocessor. Once that's dealt with, the rest is fairly mechanical.

------
kazinator
The problem is that since this Better C is not compatible with any C dialects,
and requires a D compiler, there is no reason to restrict D programs to this
subset. At least no reason that is related to simply working in something
better than C. The real reasons can be articulated better, starting with a
different name, like "Reduced D" (D that avoids garbage collection, exceptions
and/or has a smaller footprint or whatever). How about "Dm": D Minor: a sad
subset of D.

A subset of C++ is a better C, that can be written in such a way that C
compilers translate it, so that is meaningful. Well, at least a slightly
better C, anyway.

------
Koshkin
Regarding the ongoing auto/var readability issues debate, how about the
popular dynamic languages that often do not even have the means of specifying
a type? They surely must be suffering from huge readability issues?

~~~
flohofwoe
Such languages (e.g. python) serve a different purpose, at least in my
opinion. Using python as scripting language for small "adhoc automation"
things is great.

For long-living and bigger code bases, worked on by teams, explicit types and
"proper" static type system are the better choice.

E.g. it's less about readability, but maintainability.

------
teleforce
This is a nice tutorial introduction on using D as a better replacement for C
[1].

Some excerpts "At one time, C was my go-to language for most programming. One
day I realised that most of my C programs kept reimplementing things from C++:
dynamic arrays, better strings, polymorphic classes, etc".

[1][https://theartofmachinery.com/2019/04/05/d_as_c_replacement....](https://theartofmachinery.com/2019/04/05/d_as_c_replacement.html)

------
hirves_lot
I think D is great for scripting.. and having no python experience but
java/c/c++ it feels more familiar. The standard library is useful.

------
michaelmior
> To link D functions and libraries into C programs, it's necessary to only
> require the C runtime library to be linked in.

Not that I'm suggesting this would be a better approach, but wouldn't it be
possible to link both the C and D runtime libraries and wrap the main function
to do the initialization?

------
runjake
For those who have been around a while, this is not the same as DailyWTF’s
Better C dialect.

[https://thedailywtf.com/articles/The_Secret_to_Better_C](https://thedailywtf.com/articles/The_Secret_to_Better_C)

------
gok
Feels like a more accurate name would be "Freestanding D" but it's a cool
idea.

~~~
WalterBright
I actually wrote "D as Better C" before realizing "Das Better C" sounded much
better. And a shout out to all our German speaking D users!

------
kalium-xyz
So D-- then?

------
bedros
I think they should call it only slightlybBetterC

All the features I like in D such as dynamic arrays and associate arrays are
gone

------
JoeAltmaier
Ohh! Ohh! C and C++ are used. How about ℂ (double-struck C) to mean "Like C
but double the fun!"

------
0xDEEPFAC
Reminds me of Ada++

------
kreco
It's interesting how this works:

\- enumerate everything C is doing bad.

\- presents fixes with a full bag of unnecessary features that no c programmer
wants.

I wonder why there is no "Fixed C".

~~~
ddevault
Sorry to see this at the bottom of the thread, because you have a real point
here. None of the attempts at "better C" or even "replacing C" have been
serious attempts, and it's frankly getting a bit insulting. No one who still
uses C wants its replacements to "catch up" on the past 20 years of new
language development. We want C, but without the problems. We don't want D but
less so. The response below - "you don't have to use any features if you don't
want to!" \- is a ridiculous cop-out and the same thing people who defend C++
are saying.

~~~
kreco
Thanks for your comment. It's pretty normal to face this downvotes when
talking about language. And my phrasing was not the most kind of all.

It's just the CS community, at some point when you are writing languages you
tend to try to outsmart the language you are fixing by creating overly complex
behavior. Over-engineering is every single new languages.

Even Zig fails to do better than C, I think it's syntactically a disaster.

Here is a few lines from the documentation:

    
    
      const ptr = @intToPtr(?*i32, 0x0);
    
      while (it.next()) |item| { /* ... */ }
    
      pub fn main() anyerror!void { /* ... */ }
    
      exe.addCSourceFile("test.c", [_][]const u8{"-std=c99"});
    
      const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch |err| label: { /* ... */ }

~~~
snazz
Do you not like those because they're unfamiliar? What about the syntax
indicates over-engineering?

Here's what I see in your example:

\- @ indicates built-in function, which avoids namespace collisions.

\- Optional types via the ?, which solves the null pointer problem.

\- Real iterators, which are less error-prone and nicer to read than
traditional for loops in a lot of common applications.

\- The || syntax is better than something like `for (type var : array)` in
Java, don't you think? Especially since it works with _any iterator_?

\- anyerror!void is a nice way of saying that the function can return an
error. Remember that we don't have exceptions here.

\- It has interoperability with (multiple standards of) C.

\- Error handling is higher-level and less error-prone than in C without
exceptions that can create hidden jumps in your code.

So it clearly solves issues that people run into frequently in C. It brings in
a nice sampling of high-level language features without doing anything that
would compromise its niche as a systems language. It still has manual memory
management and does not have exceptions. The behavior is _simpler_ and
requires that you language-lawyer the specification for undefined behavior far
less often than in C. Your knowledge of C isn't necessarily enough to be able
to immediately read Zig code, but that's because Zig isn't C. It's not hard to
learn.

~~~
kreco
Thanks for spending your time explaining this. I think a lof people will
understand those lines better.

I actually already knew everything. I watched closely the development of Zig,
and just give up when I realized all those decisions where made carelessly in
my view or I just simply disagree with the direction of the syntax.

Since you took your time I will take mine to address what I don't like:

\- There is no way to write "int" by default. I understand why. But I don't
agree.

\- struct and enum statements (within braces) are separated by a comma which
is inconsistent with statements in function separated with ",". In my view
expect a difference between parameters and statements. parameters (a,b,c)
statement {a;b;c}

\- You have to type "var" or "const" everywhere, but not in struct statement
and enum statement. Sometimes it's like a "def" sometimes not.

\- You have to type const when importing module:

    
    
      const std = @import("std"); // Why ? Why should I specify const, this should be inferred. Same when I define a struct. 
    

\- the "undefined" keyword when it means "uninitialized" even in the
documentation.

    
    
      var my_var: i32 = undefined;
    

\- The worst, no default values for struct.

\- The list of keywords is insane: errdefer, allowzero, orelse, unreachable,
anyerror

\- About iterator I was doing well with:

    
    
      while(get_next(context,  &item)) { /* ... */ }
    

It's like everything has been carefully design to be even less readable than C
code. I have the feeling that the syntax help more the compiler than de
developer.

I wish I could bet thousands of dollars that there will be only one compiler
in the whole life of Zig language (also because it relies way too much to
LLVM).

~~~
WalterBright
> It's like everything has been carefully design to be even less readable than
> C code.

D is designed to be very readable to the C programmer. Some changes, like
replacing:

    
    
        (int)(expression)
    

with:

    
    
        cast(int)(expression)
    

is designed to make the code more readable, and greppable. Cast is a
fundamentally dangerous operation, so being able to grep-and-check for such is
worthwhile.

The D compiler actually recognizes the C form and suggests using the D form to
fix it.

Converting C to DasBetterC is largely just a) eliminating use of the
preprocessor and then b) making the syntax changes suggested by the compiler.

~~~
kreco
Yes, I can see the design of D was made carefully.

D is actually a great language. I just "don't understand" why it's not
massively used instead of more recent versions of C++, I consider newer
version of C++ as a totally different language with more drawbacks than
advantages. Half of new features are present to fix previous half baked
features. This is quite embarrassing.

Sorry for my selection of words (and for the unrelated opinion, I had to
rant), I'm not good at spending time to rephrase when the end content is the
same.

I remember one time I spent the whole day browsing the website of D,
everything actually make sense. There are still features that I wouldn't use,
but this is what I expect from a programming language design. I was impressed
by a lot of decisions.

I seemed to remember that I was a bit sad to not find proper performance
benchmarks against other languages. I was intrigued because the documentation
was so complete, it was just lacking this (maybe I just couldn't find them).
It was a long time ago so it has probably been fixed. I will give it a try at
some point.

~~~
WalterBright
The performance of D vs C and C++ should be identical for code written the
same way, as they share the same optimizer and code generators.

Also, we used to publish benchmarks. These inevitably did not produce
illumination, but long ripostes from people arguing that the benchmark was
unfair, inaccurate, nobody would write code that way, we sabotaged other
languages, etc.

We encourage people to run their own benchmarks on their own code and let the
results speak for themselves.

One issue with moving to D is it takes a while for people to learn "the D
way". For example, if they come from C they write C style code in D. From C++,
they write C++ style in D. From Python they write Python style in D.
Inevitably they'll run into some difference where D doesn't have an analogous
feature, and would get a bit frustrated (even though in D the task would be
accomplished a different way).

It takes a bit of perseverance and faith to get through that until one
discovers "the D way" and then they're hooked.

One of the reasons for DasBetterC was to reduce this issue as much as
possible, though the people who like metaprogramming with the C preprocessor
will have more work to do in getting adapted to D's powerful metaprogramming
features, which of course work nothing like text macros.

~~~
kreco
> Also, we used to publish benchmarks. These inevitably did not produce
> illumination, but long ripostes from people arguing that the benchmark was
> unfair, inaccurate, nobody would write code that way, we sabotaged other
> languages, etc.

Oh, this is pretty sad...

Anyway thanks for your time. Was nice to interact with the author of D. I
truly believe what you've done will inspire a lot of people.

I hope you didn't encounter too many people saying creating D was foolish.

I spent time checking about a lot of languages and only D (and Jai, but it's
not released) would restore my joy of programming.

