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

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: