timotheecour / D_vs_nim

comparison of D vs nim
Apache License 2.0
63 stars 14 forks source link
comparison dlang nim nim-questions

D vs nim

PR's welcome! Goal: up to date and objective comparison of features between D and nim, and 1:1 map of features to help D users learn nim and vice versa.

NOTE: tables render better (ie full width) with a chrome extension, see https://stackoverflow.com/a/49566642/1426932

NOTE: occasionally, nim code below will use the braces syntax skin to fit in 1 line

Help welcome, eg by filling in the entries with ? TODO and CHECKME, correcting wrong entries, or adding more (see also other files eg libraries.md).

category D nim 1 for D, -1 for nim
UFCS
UFCS supported everywhere not everywhere, eg: mixin(expr), typeof(expr), local symbols (called UFCS or MCS in nim) not for cast, not for templates/macros with untyped, see https://nim-lang.github.io/Nim/manual.html#templates-limitations-of-the-method-call-syntax
tuple
tuple unpacking no yes:proc getTup(): auto = (4, "foo"); let (_, a) = getTup() -1
CTFE
engine AST interpreter. every expression encountered will allocate one or more AST nodes. Within a tight loop, the interpreter can easily generate over 100_000_000 nodes and eat a few gigabytes of RAM. That can exhaust memory quite quickly. Future work: https://dlang.org/blog/2017/04/10/the-new-ctfe-engine/ uses a register VM (also the basis of Nimscript); faster -1
FFI during CTFE no no 0
embed directly C/C++ code no yes: emit -1
can read/write/exec during CTFE read only (string import) yes; allows filesystem access via staticRead and staticExec; -1
other CTFE limitations ? Heap allocated compile-time variables are turned into immutable static/const at runtime. ?
OOP
design Java like allows multi-method dynamic dispatch (defined outside, avoiding kitchen sink classes) -1
syntax
allows local imports yes no 1
import order irrelevant yes ? 1
package modules (allows backward compatible breaking of module into package) yes: https://dlang.org/spec/module.html#package-module yes but see https://github.com/timotheecour/D_vs_nim/issues/22 0
mutually recursive imports yes no, compile-time error. you can use forward declaration and/or "mixin foosymbol" to tell the compiler that a symbol will be visible at one point (CHECKME); see also: https://stackoverflow.com/questions/30235080/cannonical-way-to-do-circular-dependency-in-nim, https://github.com/nim-lang/Nim/issues/3961, https://forum.nim-lang.org/t/2114; workaround: The lack of cyclic dependencies in Nim is usually worked around by having a types module 1
familiarity C-like C or Python-like 0
interpolated strings no yes -1
named parameter arguments no yes -1
style (subjective opinion) https://dlang.org/dstyle.html ; style guide for phobos takes too much vertical whitespace (eg braces on their own line) Nim Enhancement Proposal #1 0
types
mutually recursive types yes these types can only be declared within a single type section (else would require arbitrary symbol lookahead which slows down compilation.) 1
nested types yes no; but see https://github.com/nim-lang/Nim/issues/7449 1
semantics
rvalue references no ? ?
attribute inference for template functions ? ?
Distinction between traced and untraced pointers yes -1
forward declarations allowed? yes no; see https://github.com/nim-lang/Nim/issues/5287 1
User defined operators partial: opCall opSlice, opAssign etc yes -1
User defined attributes yes ? ?
RAII yes (modulo caveats https://github.com/timotheecour/D_vs_nim/issues/27) no, see: RAII 1
prevention of null dereferencing

manual null checks required

null pointers to class objects can exist. The only way to check for them is to remember to use if (myObj is null). Lessening the impact of this, many built-in types, such as integers, fixed-size arrays, maps, and struct objects, can never throw null dereference errors, either by not being null or by treating null values as empty values (source).

manual null checks required

nil references and pointers can exist. The standard way to check for them is to remember to use if myObj == nil or if myObj.isNil. Lessening the impact of this, many built-in types can never be nil, including seqs. Only ptr and ref types (and cstrings) can be nil.

Nim also has an experimental notnil feature that enables a not nil type annotation, e.g. ref SomeObject not nil, to ensure that a variable can never hold nil. This feature was made experimental in 2018.

?
debugging
maturity
stability few breaking changes in each release few breaking changes in each release 0
community larger 1
interop
C++ Calpypso (ldc fork) allows direct C++ integration 1
C++ C/C++ code generation giving us much better interop than what D offers. Case in point: Converting to cstring doesn't require an allocation and copy; see also https://github.com/timotheecour/D_vs_nim/issues/12 -1
can compile to js yes -1
direct use no Nim emits C code and you can break in with emit pragma; C code doesn't have to be written outside nim file -1
standard library
ranges D ranges (implements empty, front, popFront) yield-based iterators ; maybe simpler to write but less efficient? not as flexible? (eg: can't do infinite ranges, bidirectional ranges) ?
variable length arrays D allows alloca see https://forum.nim-lang.org/t/499 (Variable length array) ?
slices form pointer + length builtin, T[] pending https://github.com/nim-lang/Nim/issues/5753 or https://github.com/nim-lang/Nim/issues/8256 1
strings are built from arrays yes, immutable(char)[] no, different type, so less generic API's 1
lazy functional programming (eg map, filter) yes, eg std.algorithm no (pending https://github.com/nim-lang/Nim/issues/3837, https://github.com/nim-lang/Nim/issues/8188) 1
ecosystem
contributing PR's languish forever PR's get merged way faster in nim (see https://github.com/nim-lang/Nim/pulls vs https://github.com/dlang/dmd/pulls or phobos etc but not sure how to quantify objectively; see also https://github.com/nim-lang/Nim/pulse vs https://github.com/dlang/dmd/pulse). QUOTE: Nim is magnitudes of orders easier to contribute to. Not only the compiler code is easier to reason about (at least for me), but PRs are accepted a lot more willingly. I bet such openness of the core devs makes Nim evolution faster and I hope it's gonna stay that way no matter 1.0. -1
repo split dmd,druntime,phobos single repo for compiler + stdlib making synchronization easier -1
github history highly intertwined (uses merges) almost linear (guessing it rebases) -1
issue tracker bugzilla (issues.dlang.org) github issues (https://github.com/nim-lang/Nim/issues) -1
opened/closed bugs 4588/14061 (https://dlang.org/bugstats.html) 1296/3437 https://github.com/nim-lang/Nim/issues ?
packages
packages dub: https://code.dlang.org/ nimble: https://nimble.directory/packages.xml and https://github.com/nim-lang/packages 0
number of packages (as of 2018/07/13) 1346 (http://code.dlang.org/) 704 (nimble list ) 1
tooling
format code dfmt --inplace nimpretty, not yet ready: https://github.com/nim-lang/Nim/issues/7420 1
code reduction for bugs dustmite pending https://github.com/nim-lang/Nim/issues/8276 1
REPL https://github.com/dlang-community/drepl ; https://github.com/callumenator/dabble unofficially, you can use nim secret but it does not support a lot of things. The most promising is nrpl ?
implementation
GC single shared memory heap that is controlled by its GC, thread safe, fully conservative, stop-the-world precise, thread-local heaps, a bit more deterministic and a lot faster, you can even timeframe it if you need consistent 60fps for example. Much better GC implementation for soft real-time applications because it can be paused or the max pause can be tuned. Default GC is not thread safe. GC implementation can be switched at compile-time between deferred reference counting with cycle detection (default), mark and sweep, boehm or no GC (memory regions). Untraced heap-allocated manually managed objects are available (nim distinguishes bw ref and ptr: traced references point to objects of a garbage collected heap, untraced references point to manually allocated objects or to objects somewhere else in memory) -1
compile speed faster (via dmd) CHECKME 1
is compiler bootstrapped? frontend, not yet backend yes, and bootstrapping sources can be regenerated from nim allowing compiler sources to use latest features -1
error messages uses poisoning/gagging to avoid spurious errors yes ? ?
binary sizes produced produces smaller binaries -1
shared library support linux:OK; OSX: ldc (not dmd); windows: not OK(CHECKME) ; anything that can be linked from C -1
doc
builtin doc ddoc (noisy and nonstandard) reStructuredText eg ## removesnfromL. Efficiency: O(1). (eg: https://nim-lang.org/docs/lists.html) -1
metaprogramming
variadic generics yes: void fun(T...)(T a) no; RFC: Variadic Generics; but varargs[untyped] allowed in macros; not same though, eg can't be used in template functions 1
partial type template type deduction yes no, see https://github.com/nim-lang/Nim/issues/7529 1
supported generic parameters type, alias, constant type, alias, constant 0
template constraint void fun(T)(T a) if(isFoo!T) concepts are simpler to use: type isFoo = concept a (...); proc fun(a: isFoo) -1
macro no hygienic macro system instead of string mixin; string mixin are available through parseStmt. The macros modify directly the abstract syntax tree given by the parser, before the compiler pass. It is possible to implement new DSLs based on the macro system: for example webserver DSL jester -1
backend
available backends custom (dmd), gcc (gdc), llvm (ldc) C, C++, js; WIP llvm (https://github.com/arnetheduck/nlvm) ?

See also libraries.md

features requested in latest D survey (https://rawgit.com/wilzbach/state-of-d/master/report.html) that are already supported in Nim

compilation time

runtime performance

category/benchmark D nim 1 for D, -1 for nim
functional: Zero_functional; Zero_functional fuses loop at compile-time when chaining zip.map.filter.reduce functional constructs ? (missing D entry) nim is currently number 1 or 2 against 9 other langs. The other number 2 or 1 lang being Rust. ?
webserver ? Mofuw by @2vg is faster than tokio-minihttp, the current #1 on TechEmpower benchmark ?
parsing csv files csv-blog-d csv-blog-nim; D and Nim had the same speed and same compilation time. fastest CSV parser (to parse GBs of machine learning datasets) is XSV in Rust 0

See also https://github.com/timotheecour/D_vs_nim/issues/11

similar code comparison

category D nim
longest path https://github.com/logicchains/LPATHBench/blob/master/d.d https://github.com/logicchains/LPATHBench/blob/master/nim.nim
csv test https://github.com/euantorano/faster-command-line-tools-in-nim/blob/master/D/csv_test.d https://github.com/euantorano/faster-command-line-tools-in-nim/blob/master/Nim/csv_test.nim
rosettacode examples https://rosettacode.org/wiki/Category:D https://rosettacode.org/wiki/Category:Nim

differences (not clear if pro or con)

map of corresponding features

category D nim
syntax:lexical
nesting block comments /+ +/ #[ ]#
documentation comments /** */ or /++ +/ or /// ## or ##[ ... ]##
WYSIWYG string `foo\nbar`, r"foo\nbar", etc. | """foo\nbar""", r"foo\nbar", etc.(?)
end of file (useful when debugging) __EOF__ ?
increment i++ i+=1 or inc(i)
concatenation ~ &
empty statement {} discard
null pointer null nil
checking for null if (myObj is null) if myObj == nil or if myObj.isNil
syntax:parsing
alias alias T2=T; ?; template, see https://github.com/nim-lang/Nim/issues/7090
type alias alias T2=T; type T2=T
string mixin mixin("1+1") stringMixinLikeInD("1+1") with: macro stringMixinLikeInD(s: static[string]): untyped = parseStmt(s) source: https://forum.nim-lang.org/t/1779/2#19060
UFCS foo(a, b), a.foo(b) foo(a, b), a.foo(b), a.foo b, foo a, b
expr without parenthesis auto a=fun; calls fun var a=fun returns fun
UFCS expr without parenthesis auto a=b.fun; calls fun var a=b.fun calls fun (when b is arg, not module)
static if .. else if .. else static if(foo1) bar1 else if(foo2) bar2 else bar3 when foo1: bar1 elif foo2:bar2 else:bar3
conditional compilation version(OSX) when defined(macosx)
compile time if static if when
string import import("foo"); requires -J for security staticRead("foo")
public import public import foo; import foo; export foo;
static import static import foo; from foo import nil
package fully qualified access pkg.mod.symbol(); symbol() (pkg.mod.symbol illegal)
syntax:exceptions
scope guards scope(exit) foo, scope(success) foo, scope(failure) foo, defer: foo, ? , ? ; see also https://forum.nim-lang.org/t/141/1#23104
try throw catch finally try{throw new Exception("bar");}
catch(Exception e) {writeln(e);}
finally {}
try: raise newException(IOError, "test exception")
except IOError: (let e = (ref IOError)(getCurrentException()); echo e[])
finally: discard
syntax:array
static array literal int[2] a = [1,2]; var a = [1,2]
dynamic array literal auto a = [1,2]; var a = @[1,2]
dynamic array create auto a = new int[2]; var a = newSeq[int](2)
empty dynamic array auto a = []; var a:seq[int] = @[]
indexing slice of a a[1..$], a[1..$-1], a[1..3] a[1..^1], a[1..^2], a[1..<3]
length a.length; a.len
types
initial value of type T.init (known at CT) ?
type of typeof(expr) expr.type
type name T.stringof (builtin) T.name (import typetraits)
class class A : B type A = ref object of B
struct struct A type A = object
float: 32, 64 bit float, double float32, float(float64)
pointer sized int, uint ptrdiff_t, size_t int, uint
sized ints byte, short, int, long int8, int16, int32, int64
char types char, wchar, dchar ?,?,?
functions
delegates int delegate(int, int) proc (a, b: int): int {.closure.}
decl
variable decl auto a=foo; var a=foo
immutable decl immutable foo=bar; let foo=bar
compile time decl enum foo=bar; const foo=bar
shared static __gshared a = 0; var a {.global.} = 0 ?
static TLS static a = 0; var a {.threadvar.} : int
ref return auto ref fun(ref A a){ return a.x;} proc fun(a:var A):var a.x.type = return a.x
skip initialization T a = void; var a {.noInit.}: T
attributes
purity pure {.noSideEffect.}
nothrow nothrow {.raises: [].}
safe @safe ?
GC free @nogc ?
lockfree ? {.locks:0.}
language
unit tests unittest{stmt} https://nim-lang.org/docs/unittest.html
constructor T(args) ad-hoc: newT(args), but see https://github.com/nim-lang/Nim/issues/7474
semantics
float.init NaN 0
file __FILE__ instantiationInfo; limitation: doesn't work for function caller, cf https://github.com/nim-lang/Nim/issues/7406
traits
does expr compile __traits(compiles, expr) compiles(expr)
get fields T.tupleof x.fields
metaprogramming
static assert static assert(foo); static: assert foo
library
universal type conversion a.to!T no: https://github.com/nim-lang/Nim/issues/7430
option/maybe type Nullable in std.typecons Option in the options module
path append a.buildPath(b) a / b ; does right thing on windows; NOTE: if b is absolute, buildPath returns b unlike nim
cmd line
custom define -version=foo --define:foo or --define:foo=bar
resources
tutorials https://tour.dlang.org/ https://nim-lang.org/docs/tut1.html
tools
caching compiler rdmd nim (compiles dependencies; only compiles changed file by default); https://github.com/Jeff-Ciesielski/nimr (subset of functionality)
find declaration dscanner --declaration nimgrep (but no declaration search, cf https://github.com/nim-lang/Nim/issues/7419)
fix code dfix nimfix
package manager dub nimble
install specific compiler versions digger choosenim

See also libraries.md

links

nim questions (besides entries marked above with ?)

nim questions (answered)

proc main() { echo "Hello" }

when (isMainModule) { main() }



## no longer valid points
https://forum.nim-lang.org/t/1779/1#11314 => dmd backend license was changed recently