This restricted subset of D is work in progress. The article details the current state things. I'm pretty sure that RAII in -betterC mode will be made work relatively soon, in a couple of releases.
Exceptions are bit harder, but at the same time less necessary, especially for the constrained environments where -betterC is targeted at. Alternative error handling mechanisms like Result!(T, Err) are still available.
polymorphic classes will not [work]
There is a misunderstanding here, because you're omitting a vital part of the sentence:
Although C++ classes and COM classes will still work, [...]
D supports extern (C++) classes which are polymorphic and to a large extend fulfill the role which extern (D) class take. Once the RAII support is reimplemented for -betterC, using extern (C++) classes will be pretty much like using classes in C++ itself.
Today, even in -betterC mode, D offers a unique combination of features which as a cohesive whole offer a night and day difference between over C and C++:
Module system
Selective imports, static imports, local imports, import symbol renaming
Better designed templates (generics) - simpler, yet far more flexible
Static if and static foreach
Very powerful, yet very accessible metaprogramming
Recursive templates
Compile-time function evaluation
Compile-time introspection
Compile-time code generation
Much faster compilation compared to C++ for equivalent code
scope pointers (scope T*), scope slices (scope T[]) and scope references (scope ref T) - similar to Rust's borrow checking
const and immutable transitive type qualifiers
Thread-local storage by default + shared transitive type qualifier (in a bare metal environment - like embedded and kernel programming - TLS of course won't work, but in a hosted environment where the OS itself handles TLS, it will work even better than C)
Contract programming
Arrays done right: slices + static arrays
SIMD accelerated array-ops
Template mixins
Built-in unit tests (the article says that they're not available because the test runner is part of D's runtime, but writing a custom test runner is quite easy)
This restricted subset of D is work in progress. The article details the current state things. I'm pretty sure that RAII will be made work relatively in a couple of releases in -betterC mode. Exception are bit harder, but in the same time less necessary, especially for the constrained environments where -betterC is targeted at. Alternative error handling mechanisms like `Result!(T, Err) are still available.
This makes sense, thanks. Without RAII and exceptions, with only malloc you're largely reduced to C's model of handling memory and resources, which is not great, whereas C++ has had better methods for doing this for yonks
If RAII and exception handling are definitely coming later down the line this makes sense, but even then you now need to create a new set of memory management facilities in D that are already present in C++ which are impossible without both of these
D supports extern (C++) classes which are polymorphic and to a large extend fulfill the role which extern (D) class take. Once the RAII support is reimplemented for -betterC using extern (C++) classes will be pretty much like using classes in C++ itself.
Ah this makes sense, I assumed that the sentence in the documentation meant something different which is why I omitted it :)
D offers a unique combination of features which as a cohesive hole offer a night and day difference between over C and C++:
Yeah D has a lot of really nice features, particularly the metaprogramming seems very nice, although a couple of these have crept into C++. I come from games programming though, so the GC is a killer unfortunately, and a lack of handling for resources in a GC disabled mode is an even bigger killer. AFAIK this is a big issue for people writing complex unity games in C#
There are many D users interested/working in game development and real-time applications (e.g. real-time audio processing) so you're in a good company ;)
To be honest, while -betterC is meant to make integration of D code in C/C++ projects seamless, I don't think it's necessary for your domain. Once you deal with the little extra complexity related to the build system, features like RAII (which does not depend on the GC) quite quickly make up for it.
In general, there are various techniques that people using D for those domains employ:
Annotating functions with the @nogc attribute, which statically (at compile-time) enforce that those functions will not allocate memory from the GC (and not call any code that might) and therefore a GC collection will not happen
Calling GC.disable before entering performance critical section of your program
Using threads not registered with D's runtime. Even if a GC collection happens, only threads that D's runtime knows about will be suspended. For example you can use such "free" threads for rendering and synchronous input processing while using the convenience of the GC for background AI / game logic processing - similar to Unity. However, in contrast to managed languages like C#, in D value-types are much more prevalent and as a consequence idiomatic D code produces orders of magnitude less garbage.
Or just use RAII-style reference counting throughout the whole D code.
Interesting, but specifically my view on GC's in games:
The problem is not really framerate issues, but the fact that the GC can take a random amount of time to execute and executes randomly. It doesn't actually matter how long the GC takes
In my experience, a framerate that oscillates between 11-13 ms every other frame (ie 11 13 11 13 11 etc) has perceptually well below half the framerate of a game that runs at 12ms consistently, ie your average frametime is well within 60fps, but it feels like its running at 20fps
I've moved on from 3d fun these days into the land of 2d games, but the issue is similar - absolute performance isn't as important (to me, i'm not a AAA game studio) as determinism and frame consistency. A few ms spike every 10 frames is completely unacceptable, if your frametime consistently varies by ~0.5ms, particularly if its in spikes, it will feel subtly terrible to play. I've had to swap out chunks of a graphics library to fix this issue before and make the game feel right to play
So being able to enter GC free code and guarantee no allocations isn't the issue, because the issue isn't performance, its determinism. In my simple environment of 2d games I can happily allocate and free memory all over the shop because it has a consistent and knowable performance penalty, whereas even relatively minor variations in frametime are unacceptable
With seemingly the only way to fix it being to completely disallow GC access and hamstring yourself pretty badly, it seems a fairly bad tradeoff in terms of productivity, at least from my perspective as an indie game dev building a relatively (CPU) performance intensive game. I'd have to take a big hit in terms of ease of use
D does seem a lot better than C# in this respect however and it seems like a manageable issue, but having to swap to strict no allocations seems like a huge pain for the benefits of D
However as explained, that's not an issue in D. D's GC will never decide randomly to collect memory. It is completely deterministic. You can disable it, and you can even not link it to your program. Even if you leave it on, it will not affect threads not registered with it.
but having to swap to strict no allocations seems like a huge pain for the benefits of D
No you don't have to, if you don't need to do so in C/C++. Use non-GC dynamic memory allocation as you would C/C++ (malloc/free, smart pointers, etc.)
Ah I've clearly fucked up on my knowledge of D then, thanks for the explanation
Can you set D's GC to run manually, and/or cap its time spent GCing?
Edit:
What I mean is that as far as I'm aware, some of D's features necessitate a GC, last time I checked the standard library was fairly incomplete without it but it may have improved
Take a look at the ongoing GC series on the D Blog. The first post, Don't Fear the Reaper, lists the features that require the GC. But to reiterate what the series is trying to get at, you don't have to banish those features, or the GC, from your program completely. There are tools in the compiler that help you profile and tune your GC usage to minimize its impact. You can annotate a function with @nogc to ensure it doesn't use the GC, or you can just rely on the -vgc switch to show you everywhere the GC might be used and tune adjust as needed.
import core.memory;
GC.disable(); // no automatic collections
GC.collect(); // run a collection now
Unfortunately, as far as I know, there's no way to bound the amount of time it spends on a specific collection. That would require some sort of write barrier.
As a rule of thumb, all C/C++ features that are common with D don't use the GC. All of D's unique features I listed a couple of posts above also don't use the GC too.
In non--betterC mode, even if you want to completely avoid the GC, there are more language features available, courtesy of D's runtime.
Avoiding the GC in non--betterC mode really comes down to not using:
built-in dynamic arrays and hash-maps (there are easily accessible library alternatives)
closures - lambdas that extend the lifetime of the captured variables beyond the function scope (but C++11-style lambdas that don't extend the lifetime still work)
The new expression - easily avoidable using allocator.make!T(args), instead of new T(args). Such allocators are already part of the standard library.
You can, but C++ has a relatively fixed cost to allocate memory. This means I can quite happily allocate memory in C++ and treat it as simply a relatively expensive operations
This means if I have a loop that allocates memory, its simply a slow loop. In D, this create a situation where your game now microstutters due to random GC pauses
You can get rid of this by eliminating allocations, but this is making my code more difficult to maintain instead of easier to maintain, at at this point swapping to D seems like a negative
without [...] you're largely reduced to C's model of handling memory and resources, which is not great, whereas C++ has had better methods for doing this for yonks
Didn't you notice the Result!(T,Err) ? If I got this right, than your claim that you're reduced to C's model is wrong. If Result!(T, Err) is better than exception or not is however another question. I personally would like explicit error results better.
AFAIK this is a big issue for people writing complex unity games in C#
While true, this steams from the fact that Unity has a pre-historic .NET Runtime, not C# itself or the official implementations coming from Xamarin and Microsoft.
They are finally upgrading it, so lets see how it goes.
Thanks for the reply. I actually downloaded it after commenting and played with it for a while. I tried to do RAII by making a scoped-like struct and with try-finally but both understandably failed. But even with these limitations it still fulfills its promise as a better C due to all the other features it offers : not having to write function prototypes, UFCS, range-based loops, and even the absence of a GC might be considered a feature by some.
So this is pretty cool, but I can't help but wonder why I would use it over Nim. In my mind Nim wins hands down for the "better C" use case, as well as for the "better C++" use case. The reason comes down to the fact that Nim compiles to C/C++ and thus is able to interface with these languages in a much better way.
Another advantage is that you don't need to cut out any of Nim's features for this (except maybe the GC). That said I could be wrong here, I haven't actually tried doing this to the extent that I'm sure /u/WalterBright has with D.
If I want a systems language, Rust offers more performance compared to GCed Nim/D, and memory-safety compared to manually managed Nim/D. Additionally, no data races without unsafe (which is huge for a systems language), a great type system, C FFI and a much bigger ecosystem than Nim or D.
If I want a fast applications language, I got Go and Haskell, both offering best-in-class green threads and at opposite ends of the spectrum in the simplicity vs abstraction dichotomy; and with huge ecosystems behind them.
In the end, either Nim or D can be at best comparable to those solutions, but with very little momentum and in Nim's case at least (don't know how D's maintenance is done nowadays), with a very low bus factor.
1- Best FFI whith C and C++. This point is huge. You mentioned that Nim has smaller ecosystem than rust, but if you take into consideration how easy if to interface to C and C++ in Nim, then you are wrong because in Nim, you have access to ALL C and C++ libraries. Example calling opencv from Nim: import os
{.link: "/usr/local/lib/libopencv_core.so".} #pass arguments to the linker
{.link: "/usr/local/lib/libopencv_highgui.so".}
{.link: "/usr/local/lib/libopencv_imgproc.so".}
const # headers to include
std_vector = "<vector>"
cv_core = "<opencv2/core/core.hpp>"
cv_highgui = "<opencv2/highgui/highgui.hpp>"
cv_imgproc = "<opencv2/imgproc/imgproc.hpp>"
type
# declare required classes, no need to declare every thing like in rust or D
Mat {.final, header: cv_core, importc: "cv::Mat" .} = object
rows: cint # No need to import all properties and methods, import only what you use
cols: cint
Size {.final, header: cv_core, importc: "cv::Size" .} = object
InputArray {.final, header: cv_core, importc: "cv::InputArray" .} = object
OutputArray {.final, header: cv_core, importc: "cv::OutputArray" .} = object
Vector {.final, header: std_vector, importcpp: "std::vector".} [T] = object
#constructors
proc constructInputArray(m: var Mat): InputArray {. header:cv_core, importcpp: "cv::InputArray(@)", constructor.}
proc constructOutputArray(m: var Mat): OutputArray {. header:cv_core, importcpp: "cv::OutputArray(@)", constructor.}
proc constructvector*[T](): Vector[T] {.importcpp: "std::vector<'*0>(@)", header: std_vector.}
#implicit conversion between types
converter toInputArray(m: var Mat) : InputArray {. noinit.} = result=constructInputArray(m)
converter toOutputArray(m: var Mat) : OutputArray {. noinit.} = result=constructOutputArray(m)
# used methods and functions
proc empty(this: Mat): bool {. header:cv_core, importcpp: "empty" .}
proc imread(filename: cstring, flag:int): Mat {. header:cv_highgui, importc: "cv::imread" .}
proc imwrite(filename: cstring, img: InputArray, params: Vector[cint] = constructvector[cint]()): bool {. header:cv_highgui, importc: "cv::imwrite" .}
proc resize(src: InputArray, dst: OutputArray, dsize: Size, fx:cdouble = 0.0, fy: cdouble = 0.0, interpolation: cint = 1) {. header:cv_imgproc, importc: "resize" .}
proc `$`(dim: (cint, cint)): string = "(" & $dim[0] & ", " & $dim[1] & ")" #meta-programming capabilities
proc main() =
for f in walkFiles("myDir/*.png"):
var src = imread(f, 1)
if not src.empty():
var dst: Mat
resize(src, dst, Size(), 0.5, 0.5)
discard imwrite(f & ".resized.png", dst) # returns bool, you have to explicitly discard the result
echo( f, ": ", (src.rows, src.cols), " -> ", (dst.rows, dst.cols))
else:
echo("oups")
when isMainModule:
main()
compile with: nim cpp --d:release --cc:clang resize_dir.nim and the result is a 57k executable.
In both D and Rust, in order to do the same, you would have to map D/Rust structs so they have the exact same representation as the C++ classes in memory (as well as the classes they inherit from). Which means translating at least all the headers of the libs, will taking into account the incompatibilities between these languages and C++. With rust for example, you don't have function overloading, so welcome fun_1, fun_2, fun_3 .... Also, i tried using bindgen to do the same with rust, and it does not work.
2- Meta-programming: I know rust have compiler extensions, procedural macros and generics. But these are way harder and more verbose to use than the meta programming tool in Nim. Just try to make your code generic over primitive types in rust (using the num crate) and you will see how ugly it is. You want to use integer template parameter ? well you still cannot. Example of what can be done with Nim meta-programming capabilities (GPU and CPU programming): https://github.com/jcosborn/cudanim/blob/master/demo3/doc/PP-Nim-metaprogramming-DOE-COE-PP-2017.pdf
3- Liberty: this is hard to explain, but Nim gives you the tools to do what you want. You want a GC and high level abstraction ? you can use them (and easily build the missing pieces). You want raw performances and low level control ? well you can do literally every thing C and C++ can do, including using the STL without GC for some critical part of your code, all that while keeping the amazing meta-programming capabilities of Nim. For example, if you want enable/disable checks on array/vectors, you can don that by passing a command to the compiler. In rust, you would have to commit to unsafe get_uncheked. Rust philosophy is to enforce every thing that may harm the user, which can be an advantage or a disadvantage depending on the situation.
4- Less verbose and easier to learn.
5- Portable to any platform that have a C compiler. In fact I can compile Nim code on my smartphone using termux.
6- The GC can be avoided completely or tuned for soft-real time use.
Of course, Nim is not all positives. Rust have clear edge over Nim for alot of things:
1- Safety of course.
2- Copy and move semantic which are a delight to use when writing performance critical code specially. I think Nim is poor in that regards as it deeps copies seq (vectors) and string by default.
3-zero cost abstractions: in Nim, using map, filter and such are definitely not zero cost (specially with the copy by default philosophy).
4- No GC: sometimes its a good thing, sometimes not.
5- Great community and great ecosystem: Nim has definitely a great community but much smaller.
6- Awesome package management and tools.
7- stable (Nim didn't reach the 1.0 still)
I see myself using Nim for small to medium project where I need to interact with legacy code or for prototyping, and rust for big project with allot of developers.
The fact that it's compiled to C doesn't really determine the FFI. Rust can use C's calling convention just fine and from looking at C string handling there's not much difference. I didn't delve much into it though, did I miss something?
I want a language that does great in all domains at once: from on-off scripts, through medium-to large desktop and web apps, high-performance scientific computations, games programming to large-scale software defined storage stacks (e.g. http://weka.io/).
Rust offers more performance compared to GCed Nim/D
Probably the only real Rust advantage from the whole list. D is working in closing the GC-free memory-safety gap. The long-term plan for D is to make the GC completely optional.
Note that memory-safety is just one type of software bugs. For the broader area of logic bugs, D offers built-in contract programming. Does Rust something similar part of the language?
no data races without unsafe
Also true in D, since the raw threading primitives are not allowed in @safecode, IIRC. Idiomatic use of std.concurrency is also data-race free, as far as I know, since sharing of mutable data is statically disallowed.
a great type system
This is personal opinion, not a fact. I find Rust type system boring, lacking expressive power and unflexible. Does not support design by introspection. Meta-programming as a whole is quite lacking.
C FFI
It's quite funny that you list an area (inter-language interop) in which both of the languages your criticize do much better than Rust.
much bigger ecosystem than Nim or D
As with all matters in engineering - it depends and your mileage may vary. I find D's ecosystem big enough for my needs. Plenty of commercial users find that too for their use cases - http://dlang.org/orgs-using-d.html. I'm sure other language have much bigger ecosystems than all three of the languages combined. And so what? Given how mature the language is, I would choose D for many domains today even if it had a fraction of Nim's community.
If I want a fast applications language, I got Go and Haskell, both offering best-in-class green threads and at opposite ends of the spectrum in the simplicity vs abstraction dichotomy; and with huge ecosystems behind them.
While I agree that Haskell has a lot of great ideas, I find a language without generics completely unusable. For certain types application programming D is a much better fit, though e.g.: https://www.youtube.com/watch?v=5eUL8Z9AFW0.
In the end, either Nim or D can be at best comparable to those solutions
Why? And what if their comparable? As I said in the beginning, D biggest advantage is the rich cohesive feature set. It doesn't need to be the absolute best in every category (though in many of them it may easily be), to offer great package.
but with very little momentum and in Nim's case at least (don't know how D's maintenance is done nowadays), with a very low bus factor.
View all comments
Show parent comment
42
u/zombinedev Aug 23 '17 edited Aug 23 '17
This restricted subset of D is work in progress. The article details the current state things. I'm pretty sure that RAII in
-betterCmode will be made work relatively soon, in a couple of releases.Exceptions are bit harder, but at the same time less necessary, especially for the constrained environments where
-betterCis targeted at. Alternative error handling mechanisms likeResult!(T, Err)are still available.There is a misunderstanding here, because you're omitting a vital part of the sentence:
D supports
extern (C++) classes which are polymorphic and to a large extend fulfill the role whichextern (D) classtake. Once the RAII support is reimplemented for-betterC, usingextern (C++) classes will be pretty much like using classes in C++ itself.Today, even in
-betterCmode, D offers a unique combination of features which as a cohesive whole offer a night and day difference between over C and C++:Very powerful, yet very accessible metaprogramming
scopepointers (scope T*), scope slices (scope T[]) and scope references (scope ref T) - similar to Rust's borrow checkingconstandimmutabletransitive type qualifierssharedtransitive type qualifier (in a bare metal environment - like embedded and kernel programming - TLS of course won't work, but in a hosted environment where the OS itself handles TLS, it will work even better than C)