Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I have read many posts saying D was better than C++ as a language but there were no libraries for use case X. Now this may help popularise the language.


One of D's goals is to be ABI compatible with C and C++, ABI compatibility with C is 100% as I understand it. C++ is a work in progress.

What is ABI compatiblity? in a nutshell it let's you use libraries from another programming language. In D, all you need is a D file that describes the C/C++ library as a wrapper so the D compiler knows what you are using.

This means D will build upon everything that already exists for C/C++. You can intermix C/C++ and D in the same project if you so choose to. Which is really a big deal because you don't necessarily have to stop and rewrite years of code you might already have to start using D.

Syntax wise D is a kin to C#/Java, so if you know C# or Java, jumping over to using D is just a matter of learning the language specific Gotchas and core libraries. So the learning curve is quite low if you know C/C++,C#, or Java.

to see what D libraries are out there I suggest looking at http://code.dlang.org/


How would that even work? Modern C++ libraries are largely templates, templates which must be instantiated at compile-time i.e. you need a full C++ compiler to use C++ libraries directly.

Said compiler must somehow work together with the D compiler here, which should lead to god-awful memory use, compilation time, and error messages.


Yes, it is amazing that D can work with C++ templates! I gave a talk on how it is done here:

https://www.youtube.com/watch?v=IkwaV6k6BmM


Most useful C++ code is either not templates or it's templates which can be instantiated a finite number of times and then used like non-templated code. Most "header-only"/header-heavy C++ libraries which present the problem you mention solve problems like C++'s smart for loop working like "for x in xs" does in Python but not working like "for x,y in zip(xs,ys)" does or like "for i,x for enumerate(xs)" does etc.; you don't really need these outside C++ (whether you need them inside C++ is another question but it's irrelevant here.)


That sounds ..wrong. what about the C++ standard library? Neither vector or sort sounds like that?


Those > can be instantiated a finite number of times and then used like non-templated code.

So you have a C++ file that uses vector<int>, vector<struct c_struct>, vector<CppClass>, vector<DClass>, etc. once and then you can link to them from D code.

The only case where that doesn't work is when you have lots of one-off instantiations, where requiring an instantiation in C++ code for each use in D would get annoying quickly.


Does the D compiler know how to mangle/demangle C++ names?

Do you have to write some kind of D header file for foreign C++ functions? Otherwise, how would D even know about the existence of e.g. vector<int>::push?


https://dlang.org/spec/cpp_interface.html#cpp-templates

You will need to recreate the C++ function definitions in D and mark them extern(C++), but that's about it.


Those parts of the C++ standard library are header-only and will be instantiated with concrete types at compile-time.


You should probably read up on ABI Compatibility, and, Compiling vs linking as well.

Let's be clear a compiler builds a object files that have to be linked to create a executable binary. IE compiler makes .o files. Linker takes output object files from the compiler an puts them (.o,.so/.dll,.a) together, to make .exe files.

This all works because you are linking to the C/C++ library binaries (the .o, .so, .a, .dll) files generated by a C/C++ compiling/linking. In most cases this is shared object library/dll provided by the operating system or pre-built library you install on the system. There is no need to compile it because it is pre-built. if you do compile the library yourself, it is a one time thing.

In D you create a .d file that describes what is in a C++ library .o/.so/.a/.dll file. So that upon linking, you take your D code's .o files + the C/C++ .o files to put together a full executable binary.


That's not how C++ templates work. You cannot deliver them as binaries.

Templates are delivered as C++ include files (i.e. source code). When you use them, the compiler fills in the types you want and compiles the result.

For example, there's the std::vector<> template. You can fill in any type that meets the specific requirements of the template and use it like std::vector<int> or std::vector<YourC++ClassType>.

However, you probably won't be able to do std::vector<YourDClassType>. ABI compatibility is not enough to do this as you would be effectively mixing C++ and D source code here.


> However, you probably won't be able to do std::vector<YourDClassType>. ABI compatibility is not enough to do this as you would be effectively mixing C++ and D source code here.

I think this should be working. You don't have to 'mix source code', you can implement YourDClassType in D and use it in C++. You simply have to write a C++ header describing the class. Then write a D file describing the external C++ sdt::vector<YourDClassType> file and link everything together.

Not sure if anybody actively tested this though ;-)


As mentioned before, C++ compatibility is not 100% yet. My comments are purely what works now; which by describing to D what is in a c/c++ .o file so you can link it in. In your .d file that describes the C/C++ you want to use, sometimes you will have to describe the template to D by creating D template of the C++ template.

Also if your "library" is all templates, it's not a library, it's a framework that uses other libraries. There still has to be at some point tangible code that is compiled to an object file to be linked. You will have to find all of the dependancies and make sure that they are also described in a .d file.

This might mean you have a bunch of work that you need to do in order use the c++ code you want to bring in into D. But then again, if it's a public/open source library, and you go down that rabbit hole and make it work, you just made D even better.

please see https://dlang.org/spec/cpp_interface.html#cpp-templates for more details.


> if your "library" is all templates, it's not a library

TIL STL is not a library.


I think you misunderstood your parent. A lot of modern C++ 'libraries' are header-only, because they heavily use templates. There is no object code or library to link against until you instantiate templates.


That's not really relevant to their question. Many templates are implemented via inlining/instantiation in client code by default, and as such they won't be present in the output of the C++ compiler.



> D understands how C++ function names are "mangled" and the correct C++ function call/return sequence.

How does it work in practice ? C++ name mangling is not standardized and so every compiler can implement its own scheme. So does it mean there is a list of D compatible c++ compiler somewhere ? I have the feeling that if you are using some exotic proprietary c++ compiler it won't work well.


There was some interesting discussion regarding this here recently in the comments regarding a Rust demangling library[1].

1: https://news.ycombinator.com/item?id=13706799


In practice there are two dominating standards nowadays, the Itanium ABI and Microsoft's, so it's quite tractable to handle the common cases.


Thanks, I might be biased because I have a legacy platform still using gcc 2.7, which is older than the adoption of the Itanium ABI.


> C++ name mangling is not standardized and so every compiler can implement its own scheme.

That's right, and the D compiler deals with that by customizing the C++ mangling for every platform.


I am just curious what the actual list is, it is surely not possible to support every version of every compiler on every platform.


We don't. We pick one.


I assume the idea is to be able to reuse libraries like Box2d etc where it's mostly concrete code, and that other libraries where it's mostly templates kind of just have to be rewritten.


You can port some of the templates to D, since it also has templates. Although I'm not sure if they work the same in all pathological cases.


any thing that you can do with C++ templates, could be done with D templates + CTFE plus would be far more easy to read and understand that C++ templates.


No it does not. C++ templates mean matching the name mangling. That already works. Catching C++ exceptions is being worked on now.


D seems like a more useful Rust, or is that completely wrong?


Just more pragmatic, I'd say. D isn't a "big agenda language" (to steal a line from Jonathan Blow). It's extremely multi-paradigm (some might argue to a fault). I think that's why you find people saying D is like C++ or D is like C# or D is like Go or D is like Rust. You get a little taste of everything using D.

Want function programming and purity? Check.

Want C style/low abstraction code? Check.

Want extreme C++ metaprogramming? Check.

Want C#'s LINQ? Check.

Want an improved version of C++'s STL? Check.

Want low cognitive load memory management through a GC? Check.

Want highly tailored memory management? Check.

Want high level object oriented abstractions? Check.

Want memory safety? Check.

Want systems programming? Check.

Want rapid prototyping? Check.

D, fundamentally, assumes the programmer knows what approach they should take and lets them do it. There are no "we know better" design decisions in the language. I think this might be because D is so community driven. With no real company backing D was left in the hands of enthusiasts coming from all sorts of different backgrounds to implement ideas they liked.


> think that's why you find people saying D is like C++ or D is like C# or D is like Go or D is like Rust. You get a little taste of everything using D.

That's a major problem, and not a feature.

One of C++'s main drawbacks is its size and arcane features, to the point that the language is known for being impossible to master. If all D brings to the table is an agenda to pick off C++'s complexity and drive it up even further then I fail to see what problems that will solve while it creates many others.


But it didn't drive up the complexity. It drastically simplified how a lot of features work. C++ is very difficult to master not because of the number of features in the language (it really isn't even all that featureful compared to other modern languages) but because of the thousands of unexpected details you have to know. Scott Meyers made a career out explaining them (and implored D not to make the same mistake of needing someone like him). That doesn't mean the overarching feature can't be implemented in a simple way that avoids the unintended complexity though. Anybody you ask with knowledge of both D and C++ would say that D's metaprogramming facilities are both drastically easier and more powerful than what C++ offers, for instance. It's actually shocking how much you need to know to fully understand things like template/regular type deduction (which aren't the same), initialization, rvalue behavior, forwarding references (or is it universal references...they came up with the feature before they gave it a name), what is constexper-able, reference collapsing, etc. These things are all straightforward in D because they were either designed without the edge cases and legacy behavior or left out entirely because the problem was tackled in a different, more simple way at a fundamental level.


Want unit testing? Check.

And built in at the compiler level, so it happens when you compile your program as you normally would, the tests show up while compiling, and the output does not contain the code. Always impressed me.


What's D's LINQ equivalent?


The combination of ranges and UFCS led to it just naturally falling out of the language design. It looks like this (adapted from a LINQ example[1]):

    auto names = [ "Burke", "Connor", "Frank", "Everett", "Albert", "George", "Harris", "David"];
    names.filter!(a => a.length == 5)
         .array // convert from lazy range to array so we can sort
         .sort!()
         .map!(a => a.asUpperCase)
         .joiner("\n")
         .writeln;
Ranges enable lazy processing with efficient static dispatch against arbitrary types of ranges. Those individual algorithm functions are basically all template functions that return types tailored to match the input which allows the lazy evaluation to work. When writeln asks for the first element to print it asks joiner which asks map which asks asUpperCase and so on. The results are calculated upon request, not in advance, which helps you forgo a lot of memory allocations for storing temporary results.

UFCS lets you call a function as if it were a member of the first parameter (i.e. fun(x, y) -> x.fun(y)). This lets you write it as if it were chain rather than a series of inside out function calls (i.e. `writeln(joiner(map!(a => asUpperCase(a)(sort!()(array(filter!(a => a.length == 5)(names)))), "\n"))`).

There is exactly two memory allocations in all of that. Once for the initial array and again prior to sorting because it's not reasonable to sort a lazy range. We could have reused the initial array by eagerly removing the items being filtered from it if we wanted.

1. https://msdn.microsoft.com/en-us/library/bb308959.aspx


Ranges and pipeline programming.


Sebastian Wilzbach compiled this comparison of LINQ and D range primitives:

https://github.com/wilzbach/linq


In my experience, D is like more powerful and feature-packed C#. It doesnt impose any limitations on you and your coding style/paradigm and strives to be "the one to rule them all" tool which has everything and can be used for everything from scripting to systems programming (when stdlib will be more @nogc friendly).

And yes, templates are so much better in D than in C++ or in C# :)


> when stdlib will be more @nogc friendly

How does @nogc work in D? Is it easy to keep track of what needs freeing and what does not or is it easy to mix up and get hard bugs? Also, what do these bugs look like? Is use-after-free possible or how is the failure mode i that case? Is it possible to call free on an object after it's been garbage collected?


@nogc just causes the compiler to error out when a GC allocation occurs in the region marked @nogc and its call graph. You are then expected to manage memory yourself. You could rename it @c_or_cpp_style_memory_management_only. You can malloc/free (scope(exit) is useful here), use smart pointers, alloca, static arrays, use Andrei's allocators[1], or whatever else you'd use in C or C++.

The answer to all of your questions is basically the same as they are in C and C++. D does have @safe though which prevents unsafe memory operations and Walter is in the process of ratcheting up the memory safety with DIP1000[2].

1. https://dlang.org/phobos/std_experimental_allocator.html 2. https://github.com/dlang/DIPs/blob/master/DIPs/DIP1000.md


They are very different languages. Rust was designed to be GC-free and memory safe. D, on the other hand, has GC and is memory unsafe by default.


If you add:

    @safe:
at the top of your code, it'll be memory safe (excluding issues which we plug 'em when we find 'em). I wouldn't say that makes it fundamentally different from Rust. What is fundamentally different from Rust is the approach D uses to implement memory safety.


D being @system by default has already been discussed as a design error, but making @safe be the default would be a breaking change.

So it is up to the community if they want to accept such change.

I should note that they still need some help cleaning the standard library and compiler corner cases in regard to @safe.


Could there be a compiler flag to flip the default to @safe,so it doesn't have to be added to each file?


It could eventually be an option I guess, but it would require anyway to recompile all code.

As always in such cases, to validate binary libraries, they need some kind of metadata to indicate they are safe libraries (aka they only use of @safe or @thrusted code).

.NET does this with MSIL metadata, Modula-3 does it directly on the module definition section, for example.


Well, if you actually use the GC and array range checks (also enabled by default), it gives you memory safety in the vast majority of cases. Though, indeed, the language does let you break that by default, it isn't something you are likely to do accidentally.

GC + range checks provide memory safety to most programs without the kind of extra work you need in Rust. This is a big reason why they are so common in industry.


Call me wrong if I misunderstood, but isn't reference counting also kind of GC?


Rust doesn't use reference counting (until you want to use it)


Isn't borrow-checking a sort of compile-time reference counting? Not what people usually think of when they say "reference counting", but I wonder if it's a good way to think of borrow-checking.


Not really, it doesn't count references in the same way. The borrow checker maintains a set of rules that are more expansive than reference counting. For instance the rule that you may only have one &mut at a time, and no other references as long as &mut is alive. It also has something like linear types with ownership rules, where owned values can be used only once.


Some people make this analogy, but it has so many caveats, and is so far away from what people think about as RC, and has very serious and significant differences, that I don't think it's a useful analogy, personally. I even might go so far as to say "actively harmful." Not totally sure though.


Yes, but reference counting allows for deterministic destruction. This is a game changer to leverage the power of destructors.


One of the biggest differences in practice between reference counting and GC (or "other forms of GC" if you consider RC a type of GC) is that nodes involved in a cycle will never be freed.


Yes, RC is a form of GC.


I like Rust, but I hate with a passion the borrow system. So I was looking for an unsafe-by-default Rust and I found D, thanks to the suggestion of a kind HN user. I must admit that I love it!


Interesting. I like rust, and struggle with the borrow system, but what i'm looking for is a garbage-collected Rust!


Garbage-collected Rust? I think Scala is very close, particularly with Scala-native.


D is like Go if Go had actually been a "systems" programming language. Rust is difficult to compare to other stuff.


To me, D's compile-time features like templates, code generation, etc. are some of its most important features. Lacking those and exceptions and many other useful bits, Go cannot compare to D. (I finally can claim experience on both languages after having coded Go for about a month.)


I think the point is that D targets the same set of scenarios as Go, but it's better at it for all the reasons that you've listed.


How do you figure? D is C++ with lessons learned. I mean, it's right there in the name. I find very few similarities between Go and D. Also, D is much older than Go so if anything Go would be D if D decided to not be a "systems" programming language (but it's plainly obvious that isn't the case).


Go is still a systems programming language, in the original sense of how Rob Pike explained it in the introductory presentation video about Go from 2009.


and I'm a professional basketball player in the way I explained it to my 8th grade class


I think D is more evolutionary than Rust. Rust tries to radically change the way programmers reason about their code. D is much more conventional in that regard, but accumulates all the power features from other languages on top of a "C++ fast" core and with a syntax that's familiar to someone coming from C++ (or Java and C#, for that matter).


D is garbage-collected, but can be opted out of. Rust has no garbage collector.


Rust has an opt-in garbage collector as a library (https://docs.rs/gc/). I don't know how good it is, though.


By that measure, so do C and C++.


You could implement GC in C++ using RAII (although smart pointers get you a long way). AFAIK, automatic GC is not possible with C.


It absolutely is possible, via conservative GC: https://en.wikipedia.org/wiki/Boehm_garbage_collector


What's the use case for a garbage collector in rust?


If you have to share some data across your program and can't determine the lifetime at compilation time, you can't simply rely on the Rust compiler for memory management.

The next option is to use reference counting by wrapping your data in Rc/Arc (depending on whether you need atomicity or not).

But that can still leak memory if you have cyclic data structures and can't break the cycle with Weak pointers.

At this point, what you need is a garbage collector.


IIRC Rust and D have different targets for use. Rust is for safe systems programming so that you don't get UB in C, while D is better C++. Also Rust new compared to D.


From what I've read (and I may be off), but to me it does not seem that D provides many of the features that make Rust useful: algebraic data types, typeclasses, memory safety without a managed runtime.


More like D is a Better C.


It even has a -betterC flag.





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: