Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Author of “Unix in Rust” Abandons Rust in Favour of Nim (github.com/ckkashyap)
149 points by dom96 on Feb 14, 2015 | hide | past | favorite | 83 comments


Basically he likes the way Nim compiles to C fairly cleanly. Helps him because he knows and understands C, and reading between the lines he wants to target systems that already have C compilers, but not Rust ones.

He seems to like Rust it just likes Nim better, feels currently its better suited. Actually fairly calm about it. Different people prefer different languages for a variety of reasons.

Not sure compiling to C as an extra step is going to do to reliablity or performance in Nim but we'll see. Interesting times.


> Not sure compiling to C as an extra step is going to do to reliablity or performance in Nim but we'll see. Interesting times.

At least as far as Rust is concerned, I firmly believe that compiling to LLVM IR was the right choice. When you compile to C, you're limited by what the language gives you, so you're out of luck if you want to annotate the IR with optimization info like GC metadata, precise aliasing info, or tail call annotations. Most importantly, though, in order to integrate well with debuggers like GDB and LLDB, you need exact control over the contents of the DWARF DIEs so that you can present your data structures to the user properly. LLVM IR gives you this capability, but the best you can do in C is #line.


Also it allowed Rust devs to define an interesting semantic for integer overflow (trap on debug, on release either trap or contain an undefined value) I don't think that they would have been able to use the 'undefined value' vs 'undefined whole program' if they had chosen to generate C.


> Not sure compiling to C as an extra step is going to do to reliablity or performance in Nim but we'll see. Interesting times. Having worked with languages that target C, machine code and C, I will make a few predictions:

Compiling to C shouldn't hurt reliability anymore than compiling to LLVM. Performance will likely be somewhat less, particularly as "readable C" is the target, not arbitrary C.

> Basically he likes Nim compiled to C better than just Rust. Helps him because he knows and understands C, and reading between the lines he wants to target systems that already have C compilers, but not Rust ones.

Maybe someone else can confirm, but I think right now Rust can't cross-compile; I would hope that this changes after 1.0, as LLVM makes it fairly easy to do.

I'm also wary of the mentioned productivity gains; I simply do not feel as confident in the correctness of Nim code I write as I do Rust code I write. This actually isn't solely due to the enforced move semantics of Rust, but due to a lot of the comments about Nim in the article linked in the issue; Nim and Rust surprise me about the same amount, but with Rust the surprise would yield (sometimes inscrutable) errors from the compiler; Nim was more likely to yield surprising runtime behavior.

The two languages I use most often are C and Common Lisp.

C has some surprising runtime corners, but the language (at least pre C11, which I haven't used much yet, so can't comment on) is small enough that I can keep it all in my own head.

Common Lisp also has some surprising behaviors, and it's a big language, but this is mitigated by 2 things:

1) Common Lisp allows very concise code; this very often lets me keep all of the portion of the software I'm working on in my head (versus C, where I keep all the language in my head)

2) Superior tooling; SLIME lets you very quickly drill down and look at what's happening. Combined with #1 it is very fast to find where the model of the code in my head diverges from reality, so most bugs are short lived.


> Compiling to C shouldn't hurt reliability anymore than compiling to LLVM.

How does Nim deal with the fact that null pointer dereferences, signed integer overflow, and shifts by more than the width of a type are undefined behavior in C, and therefore the optimizer can cause memory safety problems if they occur?

Edit: I just tested myself:

- Nim emits checks for signed overflow, but they don't seem to optimize to `jo`. Nim should be emitting these intrinsics where possible: http://clang.llvm.org/docs/LanguageExtensions.html#checked-a...

- Nim does not emit checks for shifts by more than the width of a type. Therefore you can get undefined behavior.

- Nim blindly dereferences null pointers, which is undefined behavior. In fact, I managed to get clang to optimize out the null pointer check here:

    echo("Counting to ten: ")
    for i in countup(1, 10):
        var k: ref int = nil
        var l = k[]
        echo($l)
In debug mode, this program throws an exception; in release mode, this program prints zero over and over for me, because clang optimized out the null pointer dereference per C semantics. I could probably make this program write arbitrary memory without using any of Nim's unsafe features.

I'm not sure if all three of these are Nim bugs, but they seem like it. Compiling to C is tricky.


Shrug, you left out:

- Conservative GC marking of the stack.

- No stack overflow check in release mode.

Compiling to C is not tricky, it is horrible and we don't do it for the fun of it! That said, clang has lots of options to tame C's undefined behaviour. Guess what, you can enable these too for C code generated by Nim.


> That said, clang has lots of options to tame C's undefined behaviour. Guess what, you can enable these too for C code generated by Nim.

They aren't really designed for performance though. For example, `-fsanitize-undefined-trap-on-error -fsanitize=null` emits explicit comparisons against null for every pointer load instead of catching SEGV like (for example) Java or Go do.

From what you're saying it sounds like maximum performance plus memory safety isn't a design goal for Nim. That's totally reasonable. It does mean that Nim and Rust have very different aims, however, and comparisons between the two need to take this into account.


> - No stack overflow check in release mode.

-fstack-check in GCC guarantees that the guard page will be hit (as long as there is one), assuming no undefined behaviour like buffer overflows. It has a negligible cost and should really be enabled by default. It only has to add a one byte write per uninitialized page on the stack, and a less efficient dynamic alloca due to probes.


Okay, that's just bad[1]. It's not hard to avoid the undefined behaviors in C with machine generated code; it's not that there are a lot of undefined behaviors, just that they are often non-intuitive. A computer doesn't care if they are intuitive or not, so avoiding them in code-gen isn't that hard.

It sounds like Nim passes the lack of safety of C up to the user a bit (e.g. in the null pointer dereferences) in exchange for improved performance.

1: And by "bad" I mean "not a tradeoff I would make." I actually can see good reasons for doing so, it just doesn't fit my priorities.


> Maybe someone else can confirm, but I think right now Rust can't cross-compile; I would hope that this changes after 1.0, as LLVM makes it fairly easy to do.

Rust can cross-compile, at least for a limited set of targets (see https://github.com/npryce/rusty-pi/blob/master/doc/compile-t... for an example)

The main pain point for the moment is that you need to build the cross-compiler yourself (which takes a while), and since the language changes every week, you have to recompile the cross-compiler every time.


You can also use the 'flexible target spec' to cross-compile for anything LLVM can.

It's still not super easy: you need a standard library that's been cross-compiled, IIRC. There's some small things like that, but it's mostly a polish issue.


LLVM's optimization passes are pretty awesome, but so are GCC's. My guess is that both methods can get you good performance, and that what performance you do get depends on how good the LLVM bitcode or C you generate is. I've tried using Rust and Nim for very small programs (Project Euler problems and some text munging), and currently it seems to me that (1) Nim's executables are always a little faster than Rust's (but not much), and (2) Nim compiles way faster than Rust.


One reason you may find text munging to be faster in Nim is that we have an different algorithm than they do that's better in the worst case but slower in many small cases. Plus, Rust's are UTF-8 aware by default, and Nim's are not.

(This doesn't mean that it's still not the same from a user's perspective, just explaining a bit about why, and that this might change in the future.)


Those are good points about string processing (and I wasn't aware of them, thanks!), but when I said Nim programs typically run a little faster I didn't mean only text munging, also my Project Euler type code: lots of loops, arrays randomly accessed and integer arithmetic. For those numeric programs the difference is pretty small, so maybe it's just the same slight edge that GCC sometimes has on Clang. (I guess I could try installing Clang, recompiling the Nim programs with Clang instead and checking if that brings their performance closer to the Rust ones.)


> Compiling to C shouldn't hurt reliability anymore than compiling to LLVM. Performance will likely be somewhat less, particularly as "readable C" is the target, not arbitrary C.

I would say "performant C" is the target, it being rather readable is nice too, though. I have a list of some benchmarks down here: https://github.com/def-/nim-benchmarksgame#nim-implementatio...


I am sure most languages could be compiled to C if you wanted. C++ started that way with the at&T cfront compiler.


Having played with both Nim and Rust, I also prefer Nim.

Basically Rust is a slightly more pleasant C++, but still huge, complicated, things break all the time, and there's no IDE support to help you make sense of anything. It's probably more robust, maybe faster as things get big, but I haven't got there yet.

Nim is as easy to write as Python, the toolchain is very easy to set up, creating Nim interfaces (with documentation) to your favourite C or C++ library is dirt simple (thanks to tools that come with the compiler), and the language is shockingly fast.

Right now, it's just so much easier to get things done with Nim. Reminds me of Coffee-Script - because it compiles to another language, you get access to a million libraries and platforms for free.


It's a shame that it doesn't have the mindshare: 27 nim/nimrod questions on SO, 1548 rust. Still not "notable" enough for a Wikipedia entry. I can see nim getting stuck lingering on the margin like Objective-C did before OSX.


What if that's just because people have fewer problems with Nim than they have with Rust? The number of SO questions is a better measure of how often and how many people run into problems than it is a measure of overall popularity.


It's possible. I personally just hope that Araq and others keep working on it. Right now it's quite easy to use without a lot of libraries written in Nim, because of interoperability.

Besides, I see Nim more as a 'secret weapon' sort of language, or something that individual programmers use. Rust is more of a 'big idea' language, so it makes sense a corporation is pushing it.


> Besides, I see Nim more as a 'secret weapon' sort of language, or something that individual programmers use. Rust is more of a 'big idea' language, so it makes sense a corporation is pushing it.

I don't think that "big idea" languages [1] are necessarily the provenance of corporations/committees as opposed to individuals. If anything, the opposite tends to be true, as large languages such as C++ and C# have tended to grow to encompass more and more features over time as their feature sets expand to accommodate more and more stakeholders. Rust in particular started out as an individual project, with a set of design constraints—those of low-level memory-safe systems programming—that have remained unchanged throughout its lifetime.

[1] I'm not really a fan of that term anyway, as "big ideas" are just design constraints, things that all languages have. Rust just happens to have a unique (among industry languages, save maybe ATS) set of them.


> Rust is more of a 'big idea' language, so it makes sense a corporation is pushing it.

Araq just announced that Nim has financial backing now: http://forum.nim-lang.org/t/870


Fantastic. And thanks for that link. I was messing around with some 3D stuff in Nim, and he's working on 3D C++ stuff too I see...


Is there a flagship project using Nim ? Rust has Servo. I've read here and there that this is the best way for a language to catch on.


There was a discussion about this on r/rust before: https://www.reddit.com/r/rust/comments/2vqy81/author_of_unix...

My reasons for using Nim can be found in my blog: http://hookrace.net/


Here is the unix kernel (in Nim) that he mentions: https://github.com/ckkashyap/nim-xv6. It is based off of the xv6 OS: http://pdos.csail.mit.edu/6.828/2014/xv6.html


I think the reasoning is sound, but this isn't an indication of either language being better than the other. Only that it's easier to look under the hood of Nim. I find Nim easier to read, and then as is with ruby, probably more productive for prototypes, but I'd like to see more use cases before I start to move in that direction. It does make me wonder if we'll ever see a Nim to rust transpiler.


> It does make me wonder if we'll ever see a Nim to rust transpiler.

Impossible, unless you compile to the unsafe Rust dialect. Nim is not memory safe in multithreaded mode, last I checked, while Rust is. So (unsafe) things expressible in Nim will not be expressible in Rust, because Rust's type system prevents them.


2 different languages for 2 different use cases. Seems to me that Nim is more in league with Go and D while Rust is lower level with some syntactic sugar. Might be wrong but that's how I feel about these. Anyway it's great to have new french languages that you can still link to C libs when you need it.


I would agree. I don't think they compete directly, the only real similarities is they both compile to native code, and both are fast.

Nim is more of a Pascal/Python/Coffeescript hybrid with easy interoperability with C and nice tooling.


Edit: meant fresh not french ,of course.


> It does make me wonder if we'll ever see a Nim to rust transpiler.

I'm not really sure what you'd get from that? They seem to share most of the same features, with rust focusing a bit more on safety via its type system. I'm personally more partial to Nim since it seems simpler to me, but Rust may be good for things I don't program regularly.


What is Nim's community like? I ask because I first learned about Nim a few weeks ago when some people were chatting about it on Slashdot. One comment [http://slashdot.org/comments.pl?sid=6771453&cid=48860921] quoted from that day's Nim irc logs and well it was a little disturbing. There was a lot of insulting and name calling going on and it didn't leave a good impression on me. I don't want to judge the entire Nim community of course but I was taken aback by those many very negative quotations. I don't think I've ever seen that kind of animosity in the Rust irc channels I mean.


In my experience it's very responsive and rational. It benefits as a language from being opinionated which is sure to ruffle feathers from time-to-time. Obviously not as big of a community as some others, but that hasn't been an issue for us.

I've seen heated flamewars about important issues rarely, about non-important/emotional issues only very rarely and only in IRC (where the current official policy is "we're a new language so don't kick someone out unless absolutely necessary"). More often with Nim you'll see the occasional "wtf- who implemented this? it needs to change to ---". To be honest, IRC isn't the best place to judge a "community"- trolls and good people venting have disproportionate voice there. And to be honest, if that exchange referenced was disturbing... just hope you never have to be involved in a decision with the Linux kernel team or (the most abrasive example I have first-hand knowledge of) the Apache development team 10 years ago ;-)

Notice there's only one comment there from Araq and it's trying to de-escalate things. If you were to pop onto IRC and ask something technical or philosophical that wanted a rational response Araq or many of the others not represented in that slashdot comment will most likely answer very politely and rationally within a minute or so (I think he's in Germany though? so timing may be an issue).

I think a better "feel" for a community can be gauged by the tone and quality of blog entries coming out and threads in github issues / pull-requests than IRC snippets.

That and the fact that if you decide to use Nim based on its technical merits then you _are_ the community- and at this early stage you can easily influence the tenor of discussions and decision-making for good if you choose.


Rust takes a completely different approach to this. If you behave badly, you're out. Even on IRC. It also keeps discussions direct and accusal-free most of the times.

It's one of the reasons I chose Rust as the next community to work in. I have zero tolerance for such things and hate being in discussions with people that cannot - well - discuss. I just don't want to waste my time on such communities.

Given that Rust now has a surprising number of meetups around the world (Berlin alone has a regular learners group with ~ 25 attendees weekly (some regular, some new)), they have a knack for good community building. And keeping insulting behavior to zero is one important cornerstone of this.

I've been doing community org and tracker triage for quite a few years now: you _can_ and _should_ judge a community by their community spaces (IRC, trackers, bulletin boards) and not by single-person publications (GH, blogs).


Everybody was embarrassed about that day. It was a lot of toxicity over an issue that had already been resolved in technical terms, and dragged on for hours, but Andreas was mostly gracious throughout. I give him a lot of credit for being willing to trade blows with aggressive users without using Linus-style shame tactics himself. However there is also some recognition of how disruptive it is to allow discussion to spiral out of control like that. The channel is considering a more policy-driven approach to handling these kinds of situations. It has never banned anyone in its history to date, but it may have to cross that bridge soon.


I am from the Smalltalk community (very, very friendly) and just recently got into Nim but my experiences so far has been positive. Sure, more "heated discussions", but I have so far attributed that to possibly younger participants and also a strong IRC presence. I would also say that most of the harsh language I have seen has had a "tongue in cheek" tone to it. Also, I would claim its a very selected few people that like to engage in that kind of heat - thus not representative in any way. But also good that its brought up - I don't like such a tone either.


I would say it is unfair to judge any language by their IRC channel unless they are core members of the group.

In my experience IRC channels being rude is the norm rather than the exception.


> In my experience IRC channels being rude is the norm rather than the exception.

But it doesn't have to be that way. Rust has a very clear code of conduct (http://www.rust-lang.org/conduct.html) aimed at avoiding exactly that kind of rudeness, and it has so for a long time. As a result the #rust IRC channel is a very pleasant place to be.


In my experience many IRC channels are fine. If you run a project's IRC channel, there's really no excuse for forcing users to deal with a bad community, and a bad community is an excellent reason to find another project to spend your time with.


You're right, a more hands-on approach is needed. We've discussed it a bit, and no opposition has been raised to using +q more often.


I've hung in the nim community for a few weeks and I've found them extremely helpful. You do get heated arguments but it goes to show the passion. Do not let a few individuals showing off their bullying skills turn you off from a wonderful programming language. IRC is IRC.


You've been hell banned. Message the mods. Best of luck.


We just switched from Rust to Nim for a very large proprietary project I've been involved with after rejecting proofs of concept in Go and Erlang earlier in the process. This despite the fact that we had formally decided on Rust, had tons of code developed in it, and only stumbled upon Nim by complete accident randomly one day. Even though many large components were already developed in Rust we have already reached par and have shot ahead of where we were at. I don't want to diss Rust in any way (or Go for that matter), but I figure they both have corporate backing and can easily speak for themselves, whereas Nim does not- so I don't feel too bad offering our somewhat informed opinion :-)

In a nutshell, Go ended up not really being a systems language and Rust has the beautiful goal of memory safety and correctness without garbage collection but in the end feels like it's designed by committee.

Nim, with Araq it's benevolent dictator, has had the opportunity to be highly opinionated. Much of the time that seems to simply allow projects to either shoot themselves in the foot or stagnate, but occasionally it produces true gems- and I really feel like that's what Nim is.

Some aspects of Nim that keep blowing me away more and more:

* Truly allows you to be close to the metal/OS/libraries (most beautiful and simple ffi ever) AND simultaneously allows you to think about the problem at hand in terms of the problem- i.e., high level abstractions and DSL creation like you can do with Ruby (but even cleaner in many ways). I always thought those were two opposite ends of a spectrum and didn't expect a language to even attempt to bridge the chasm- then along comes Nim and not only attempts but succeeds beyond what I ever would have thought possible. To illustrate: it has become my go-to language for quick scripting, faster to whip something together than shell or ruby or perl (and many orders of magnitude faster to run- often even including the automatic compilation the first time)- while also being the fastest, cleanest, safest way to do something low-level, like a mmapped memory-mirrored super-fast circular buffer or cpu-cache optimized lock-free message passing...

* Even though it has C as an intermediate step, it doesn't feel or act anything like C. While not as strong as Rust in this area (I know, Rust goes straight to LLVM's IR instead of C- but the risks are the same)- it generates code that is much more safe (and concise) than writing it straight in C. I know, there are other language that do this well also- but usually by sacrificing the ability to easily and cleanly do system-level coding- like a raw OS system call, for example.

* On a related note, despite feeling more high level than Rust, it can do so much more at a low level. For example, using musl instead of glibc (which at least last time I checked wasn't possible in Rust due to some accidental coupling).

* So fast. While premature optimization is considered the root of all evil, and linear optimization often does not end up adding significant real value, I've been reminded more in the last few weeks than ever before that when speedups are around 2+ orders of magnitude _it is often a complete game changer_- it often allows you to model the problem in a completely new way (don't have a generally understandable example for Nim offhand but take docker vs dual-boot for an extreme non-Nim example- VMs were a mostly linear improvement over dual-booting and other network-based workarounds, but only with linux containers and their orders-of-magnitude "startup" time, snapshotting, etc. etc. was docker able to say "these are so cheap and fast we can assume a single running process per 'image'").

* Even though it's not as formally safe as Rust yet, in practice it feels and acts as safe, without the cognitive overload.

* Rust gets tons of new commits from tons of contributors every day. I worried when looking at Nim that the lower volume of commits meant that the language was less... alive or long-term-tenable. But on closer evaluation I realized that there was literally less that needed changing, and that the changes that seemed to need code to change all over the Rust repository would, given an equivalent scope in Nim, require trivial changes to just one or two files. (for example, Rust's massive [and extremely slow] bootstrapping pipeline and parsing architecture, vs Nim's built in PEG syntax and 5-line loop that keeps compiling itself using the last iterations' outputs until the outputs don't change anymore).

In short:

- All the simple beauty (IMO) and conciseness of a python-like/pseudocode-like syntax without all the plumbing and pedantic one-way that comes with python. In other words, beats python at python's own strengths (not even mentioning speed or language features etc...)

- Top-down modeling and DSL affinity like Ruby with less feeling of magic- e.g., better "grep"-ability and tracing. In fact, the DSLs and up being cleaner than idiomatic Ruby ones. So beats Ruby IMO at one of Ruby's core strengths.

- Seems as safe as Rust with much cleaner syntax and simpler semantics. (this is something we're actively quantifying at the moment). Easily 10x the productivity. _Almost_ beats Rust at its core strength.

- Easiest FFI integration of any language I've worked with, including possibly C/C++.

- All the low-level facilities of C/C++ without the undefined-behavior, with better static checking, much better meta-programming, etc. etc. Beats C/C++ at their core strength.

- Utterly intuitive obliteration of autotools/configure/makefile/directives type toolchains required for system development using C/C++.

- It's very fast and efficient per-thread garbage-collection (when you want it) essentially allows it to eat Go and Java's lunch as well.

- It's a genuinely fun language (for us at least).

I've already been too verbose and am out of time but I should include for some sense of completeness some current weaknesses of Nim. None of these ended up being remotely show-stoppers for us, but at least initially we worried about:

* Too much attention to windows (doing nix and even linux-only development is so easy it has turned out to be a non-issue).

Less safety than Rust when doing manual memory management (hasn't been an issue and we believe unlikely to be an issue in practice).

* Lack of (implied) long-term corporate support (already more stable than some others and community is strong where it counts. Also, no matter how much corporate support they get- Rust will still be more verbose and designed by committee and Go will still fall short of being a true to-the-metal systems programming language and neither will ever have Ruby and Lisp's moldability and endless potential to get out of your way).

* Smaller community/package ecosystem than many others (so easy to do FFI or to even _reimplement something done in C using 1/5 the code_ that it also has turned out to be a non-issue. Now I worry that it's so easy to code that it will have a library-bloat issue like Ruby requiring something like ruby-toolbox)

* "How have I not heard about this before?? Why isn't it bigger? Is something wrong with it?" I start worrying about things like D's standard-library wars or lisp & scheme's utter flexibility combined with poor readability... Nope, just new and only spread through word-of-mouth, like Ruby, Python, and Perl long ago...

[there, once I've written three separate lists in a single comment I can safely say I've said too much and should get back to work].


> - Seems as safe as Rust with much cleaner syntax and simpler semantics. (this is something we're actively quantifying at the moment). Easily 10x the productivity. _Almost_ beats Rust at its core strength.

> Less safety than Rust when doing manual memory management (hasn't been an issue and we believe unlikely to be an issue in practice).

One major safety issue with Nim is that sending garbage collected pointers between threads will segfault, last I checked (unless you use the Boehm GC). Moreover, Nim is essentially entirely GC'd if you want (thread-local) safety; there is no safety when not using the GC. So Nim is in a completely different category from Rust; Rust's "core strength" is memory safety without garbage collection, which Nim (or any other industry language, save perhaps ATS) does not provide at all.


So since this is such an amazing language.. I wonder why it doesn't have a Wikipedia article? Oh, wait.. I remember. Its because Wikipedia admins are an incestuous cabal and will do anything to avoid admitting one of them was wrong. Or because Wikipedia in general is a joke.

Think I an exaggerating? I believe this is the best programming language out there. Just try to add a Wikipedia article. Not in a million years.

The Wikipedia notability rules and process are ridiculous and completely unfair, when every porn star, popular smut video on the internet, rare mushroom, and Pokemon DVD has an article.. But the best programming language in the world cannot.

This is an example of what is wrong with our society.


I love how Nim is getting along but I am rather afraid to put in production.

The number of compiler bugs is a bit scary.

https://github.com/Araq/Nim/labels/High%20Priority

And also from what I've heard, the tooling isn't very good. Autocomplete isn't context sensitive and using GDB to resolve a variable like "foo" actually becomes "foo_randomnumber".


> The number of compiler bugs is a bit scary.

This is actually one of the things that keeps turning me away each time I try Nim. All software has bugs, got it, but in my mind a language nearing 1.0 should squash some of that list (or remove/feature gate things causing them) before even thinking about a 1.0 IMO.


In my opinion, 1.0 is about the language specification becoming stable. That said, some experimental features have actually been gated in preparation for the 1.0 release.


> The number of compiler bugs is a bit scary.

I see similar lists for GCC when a branch is underway. I'm actually impressed with the number of active contributors, and I find the design very compelling. I'll be keeping an eye on this project.


As for scripting: I just tested on Debian Jessie, and at least for trivial code (read: not nim itself) -- nim seems quite content to work with tcc. Tcc is a pretty awful choice for c compiler in general -- but while my current desktop is a little too fast to be able to tell -- nim w/tcc was typically as fast on first compile (read: compiler not cached in ram) as clang/gcc were on second run). Honestly, on this box:

    time ./bin/nim --cc:tcc c -r examples/hallo.nim
vs

    time ./bin/nim --cc:gcc c -r examples/hallo.nim
    time ./bin/nim --cc:clang c -r examples/hallo.nim
is a toss-up -- and they all lose against:

    time python3 -c 'print("Hello, world")'
(by an order of magnitude that ends up being almost insignificant, it's ~0.5 seconds for clang/gcc on first run, ~0.2 seconds for tcc and ~0.02 seconds for python). But the binary tcc makes runs in ~0.001 -- or basically too fast to time -- gcc/clang versions are presumably faster).

Normally I think the startup time for python is less than instant (especially without dropping some standard includes/search with -sS) -- but apparently when running on a quad-core i7 at ~4Ghz with the OS on an SSD -- it makes no practical difference. I'll try later on my slower laptop (which is slow enough that "python -c "print('hello')"" doesn't feel quite instant) -- but the main point I wanted to make was that nim -c -r with --cc:tcc makes for a quite usable "scripting" tool, thanks to tcc's compilation/startup/parsing speed (if nim w/gcc/clang wasn't fast enough already).


Wow, that should have been its own post! Great to hear how you get along with Nim.


I also apologize beforehand for a long reply. Long posts get long replies.

I can definitely understand this point of view, but I just can't agree. The parent probably wants his/her claim that Nim seems as memory-safe as Rust to not be interpreted literally, as a literal interpretation would make the statement false (by any fair comparison using idiomatic code from both languages to accomplish the same thing).

What the parent is surely talking about is how it pans out in practice. Different languages have their different trade-offs here with different pitfalls, and denying that Nim can crash and burn due to memory management mistakes would be false. Denying it with respect to Rust would also be false, due to Rusts optional unsafe features, but the important distinction is how easy it is to make these mistakes in idiomatic code and what the consequences will be. Only time will tell, which is why anecdotes are of interest, of course - both the parents and everyone elses.

However, I find some choices of words to be a bit disingenuous (though hopefully unintentionally so).

The claim about being able to do "so much more at a low level", like e.g. being able to switch out libc variants, which allegedly is not possible in Rust due to accidental coupling. Is this a temporary difference? If so, it may only be relevant in the short term. I can't answer this question, but it would be interesting if someone did.

Most importantly: "Even though it's not as formally safe as Rust yet, in practice it feels and acts as safe, without the cognitive overload." Yet? Making Nim as formally safe as Rust would require completely changing key aspects of the language. Feeling as safe is possible, and acting as safe is possible too...

... until it doesn't anymore, that is, because the team grew (as it always does, some leave, some join, etc) and the code base ballooned and someone made a simple memory management mistake somewhere that is now a serious debugging problem and no code can be eliminated beforehand from the necessary auditing because the entire code base is vulnerable to these classes of errors.

Memory management errors have a way of resulting in seriously trashed core dumps, etc, sometimes severely complicating and limiting debugging possibilities. Where's my stack trace? Oh, we seem to have been executing data and not code. Where did we come from? Oh, no intelligible stack frames. No valid return address in the register, etc. I've been there, as I'm sure many of us have. Memory management errors can lead to complete debugging nightmares, and that's if they're even reproducible by developers. If they're only triggered at the customers site due to their unique circumstances, good luck. Having a deterministic test trigger it and being able to run it through valgrind until it's solved is the optimal cake walk scenario, but that's not real life most of the time.

Rust can step quite easily from low-level stuff to high-level features and meta-programming too, and I feel no comparison is really made by the parent, only talk of Nims features. The central premise as always for Rust is that it provides what it can provide while still maintaining memory safety. Rust without this prerequisite would not be Rust, and the constraints for everything else flows from it.

The repeated claim of design-by-committee is also not the best one. Having followed Rusts back-and-forths for years, I have to say I feel the discussion has been extremely well functioning, and most importantly: The choices have been very pragmatic within the constraints of preserving the key safety features of the language.

Personally, having gone through many languages all over the abstraction level spectrum and specifically having spent quite some time in embedded C/C++, I am terribly, horribly tired of fatal runtime errors in general and memory management errors in particular. They can cost so much time to debug and fix that development time can swoosh past what it would have been in a language with a type system preventing them in the first place. Your mileage may vary, of course!

There is something to be said for languages that simply eliminate these classes of errors compile-time, and that something is actually a lot. For the small programs, tooling, scripts... I can write them in anything. There are hundreds of choices. That's not what this is about. For the software that matters, that ships and that others will expect to work, I no longer have the patience or tolerance for these error classes.

Many languages with such safety guarantees (and Nim is not one of them) have already existed for a long time, but very few that can be applied to all the use cases that Rust can. That is what it's about. This is why people are excited.

Software development is a form of art and a form of engineering, at the same time. A lot of software doesn't have to be as reliable as space shuttle firmware, and I'm not claiming it has to, but the general bar could sure as heck be raised several notches. We know how the world works, and yesterdays quick hack or proof of concept is todays firmware shipment for use in live environments. Successful software lives for a long, long time. Software is eating the world, and society is now at its mercy.

Personally, I will sleep so much better knowing that these error classes were wiped out compile time in 99.?% of the code I shipped to those customers, while being able to maintain on par performance with the C code it replaced.

These are of course my $0.02, and I hope it didn't come across as combative as that was definitely not my intention - only passionately conveying my own perspective. :)


Thanks for the thoughtful response- it didn't come across as combative at all to me.

The true, provable safety of Rust was what drew me to it as well. I've always hated having to choose between un-principled memory management (with it's security and functionality vulnerabilities that can lie dormant for many years before kicking your butt) and garbage-collection forcing you away from the metal and removing deterministic reasoning about memory usage, runtime behavior, and runtime overhead.

I've been going through the academic papers, forerunners, and source-code for Rust's static memory routines and borrowing semantics. My hope and suspicion is that it can be added to Nim without core changes to the language like lifetimes. It's definitely not a guarantee, but with lots of experience in both languages now I feel very strongly that adding region-based-memory-management to Nim is possible while adding Nim's clarity, abstractions, and efficiency to Rust feels impossible.

I agree that at the moment Rust is the only responsible choice right now if provable memory safety is a primary concern, but I suspect that will change. In the mean-time, for us anyway, the price was too high in productivity when we discovered that we could do manual memory management in Nim in very well-considered isolated places and confidently use Nim's fast, real-time deterministic per-thread garbage-collection for everything else without a noticeable performance penalty.

Having said that, I don't think I actually disagree with anything you said (:


> I've been going through the academic papers, forerunners, and source-code for Rust's static memory routines and borrowing semantics. My hope and suspicion is that it can be added to Nim without core changes to the language like lifetimes. It's definitely not a guarantee, but with lots of experience in both languages now I feel very strongly that adding region-based-memory-management to Nim is possible while adding Nim's clarity, abstractions, and efficiency to Rust feels impossible.

I'm not so sure. The trickiest part of getting memory safety without garbage collection working is not the lifetimes but the borrow check, which relies on inherited mutability and, most importantly, the lack of aliasable mutable data. The APIs and libraries of garbage collected imperative languages invariably depend on aliasable, mutable memory. Consider something as simple as a tree or graph data structure with mutable nodes. Or consider taking two mutable references to different indices of an array, or splitting an array into mutable slices with dynamically computed indices. These are all things you (presumably) can do today in Nim, and a borrow checker would break them. The likelihood that the library APIs depend on being able to do it is very high.

I never say never: you could implement multiple types of references, some GC'd and some not, and copy the Rust borrowing semantics. But they would be incompatible with most existing APIs and libraries. I don't think it can be realistically retrofitted onto a language without breaking most APIs: aliasable, mutable data is just too common.

Regarding efficiency/performance, what in particular seems impossible to add to Rust?


Thanks for your reply! An enjoyable exchange in the midst of what often feels like a bit of a very tiring flame war.

I am constantly on the lookout for languages that could be suitable for replacing (or greatly diminishing) the use of C/C++ in my work, and so far Rust is one of the front runners.

However, I am also very much aware of some of the troubles I would most likely face in convincing my colleagues, like language complexity and productivity, and I completely respect the decision that it may not be worth it, depending on a wide variety of factors.

I try to keep an open mind, and I look forward to reading more about the improvements to Nim you envision! Thanks again (and good night). :)


To be fair to Nim, I don't see any reason why it couldn't be made memory safe by using the Boehm GC (though I'm not an expert in Nim by any means). Of course, using the Boehm GC negates the advantages of the thread-local heaps, but I don't think that Nim's implementation of them scales up to large-scale software in any case for the reasons I detailed in my other comments. IMHO, if you have a garbage-collected, multithreaded language that must compile to C (and doesn't need interoperability with a reference-counted object system like e.g. Swift does), the Boehm GC is the best choice.


Thanks for correcting me, Patrick. I certainly didn't mean to be unfair (especially as I replied to a comment I felt wasn't being completely fair itself, intentionally or not), and I should have been more precise about the use cases.

I agree about the GC considerations. I meant my points to mainly apply to the use cases where safety such as that offered by Boehm is eschewed in order to achieve other powers at its expense, which I feel is brought up a lot by Nim proponents as strengths during these discussions.


> The claim about being able to do "so much more at a low level", like e.g. being able to switch out libc variants, which allegedly is not possible in Rust due to accidental coupling. Is this a temporary difference? If so, it may only be relevant in the short term. I can't answer this question, but it would be interesting if someone did.

It's definitely intended that the Rust standard library can compile against many libc's. I personally hope that it can eventually be completely self contained and not even link to libc in certain configurations.


Thanks for clearing that up!


Have you read the following article?

https://gradha.github.io/articles/2015/02/goodbye-nim-and-go...

I'm curious about your thoughts on it being that the author speaks about some areas you're currently talking about.


Could you elaborate a little about why you rejected Erlang?


I actually coded a predecessor to the system under development a few years ago and have used Erlang and more recently Elixir a lot, but in the end it just wasn't low-level enough, fast enough, or runtime-free-enough for our current project (for example we need to generate real-time processing kernels that can run on GPUs and FPGAs-- a realm not even really contemplated at the Erlang level).


Isn't Nim GCed? How are you going to build a kernel like that?


GC in Nim is highly configurable: you can pause GC or even delay it indefinitely. Plus there's lots of tweaks you can make to how GC treats your code using Nim's pragma system. And no, the standard library does not rely on GC like in D.


My issue with this approach is that it seems inherently racy in multithreaded programs. Nim gets around that issue by having a per-thread GC and segfaulting if you send pointers to other threads (unless you use the Boehm GC, last I checked)—your only backup seems to be manual memory management—but I don't see that as a scalable approach for large-scale software with dozens or hundreds of threads. (Not to mention that not having a thread-safe GC neglects a major benefit of a GC for lock-free data structures; having to use hazard pointers if you're a GC'd language seems like a shame.)


> And no, the standard library does not rely on GC like in D.

Does this mean that all of the standard library is written using manual memory management? Wouldn't this slow down its development and expose it to all the same reliability and security problems C has?


> And no, the standard library does not rely on GC like in D.

This isn't correct, it relies heavily on GC.



The presence or lack of a GC has nothing to do with the Kernel. The Kernel is simply another program, with specific requirements and limitations. So long as your language allows you to write and call raw assembly, you can write a Kernel.

It's worth remembering that Rust has a form of GC as well - it uses a reference counting collection mechanism which is executed at scope boundaries instead of as a separate thread or co-routine. This doesn't prevent writing Kernels or userspace applications in Rust.


I'll note that Rust's reference counting system is not a full garbage collector - it can't detect cycles. As a result, if you create cycles, you leak memory.

You don't want a garbage collector in a kernel, because there's a lot of code that needs to run in soft realtime - aside from timekeeping, there's lots of hardware that'll enter a failure mode or drop data if you don't respond to it in time.


> As a result, if you create cycles, you leak memory.

Eep.

> there's a lot of code that needs to run in soft realtime

This is not really a problem, since the hardware will trigger an interrupt which will, at the hardware level, stop the current instruction and move the instruction pointer to a registered location in code. Most time-sensitive hardware interaction occurs in these interrupt handlers, the kernel simply has to read/populate buffers with data which the interrupt handlers consume/fill.

Since interrupts happens to existing kernels all the time (as well as user-space GC runs as well), and so long as the interrupt handler can properly run without having to mess with memory being managed by the GC (which might, to be fair, require a bit of lower level code), you could resume back into a GC cycle as easily as you can resume into a normal kernel.


> This is not really a problem, since the hardware will trigger an interrupt which will, at the hardware level, stop the current instruction and move the instruction pointer to a registered location in code.

At which point the interrupt handler needs to route the interrupt through a complex, multi-layer driver stack, and the driver needs to respond - where it's likely to depend on complex datastructures of the type that would normally be managed by a GC if the response is anything but an absolutely trivial ack.

All garbage collectors require that at least some memory is inaccessible to regular code while the garbage collector is running. As a result, you essentially have to ensure that anything that might ever be touched as a result of an interrupt, or during another soft-realtime event, is not managed by the garbage collector - and then you may have to detangle even more data from the GC's grasp when the hardware vendor releases the next revision of their hardware which requires access to more information. You also have no proof that what you're doing outside the safety of the GC is correct without a Rust-like lifetime system, or other more complex static verification systems (such as have been built on top of Coq).

In addition to that, developing performant code with few/no obvious hangs under a GC takes a lot of effort and specifically fighting against the GC - ask any game developer who's developed a large game using C# under Unity or the like - and they get the luxury of getting to run GC at vsync. You don't have any good spot to run GC in a kernel.


> It's worth remembering that Rust has a form of GC as well - it uses a reference counting collection mechanism which is executed at scope boundaries instead of as a separate thread or co-routine.

And it's opt-in: you only pay for reference counting if you explicitly wrap your type in Rc. Moreover (and in contrast to Nim), you don't need to use reference counting for memory safety.


> you don't need to use reference counting for memory safety.

If you limit yourself to boxed and stack variables, which is a pretty big limit when writing larger programs.

And as noted by a sibling comment, you can create a memory leak due to cycles if you use it imprudently - do we consider that to be a safe use of memory?


> If you limit yourself to boxed and stack variables, which is a pretty big limit when writing larger programs.

That's more or less how most C or C++ programs work, however. C and C++ programs (including the Linux kernel) use reference counting when the object lifetime is truly dynamic. For example, file objects going back to the very first Unix have had to be reference counted, because they can be duplicated via the dup() system call, inherited via fork(), or sent from process to process via cmsg.

The big advantage of the C/C++/Rust approach comes from the observations that (a) the vast majority of objects only have one owner; (b) reference counting does not have a global performance impact when used only for a small subset of objects.

> And as noted by a sibling comment, you can create a memory leak due to cycles if you use it imprudently - do we consider that to be a safe use of memory?

It's a tradeoff: reference counting is bad at cycles, but has advantages due to prompt deallocation, avoidance of the mark phase, and (in Rust) avoids unnecessary cross-thread synchronization. I don't think it's unsafe, because leaks can happen in any language, including garbage-collected ones: you can have arrays in global variables that grow without limit, for example.

Remember that the criterion that GC uses—no more references to an object—is ultimately a heuristic approximating what you really want, which is whether the program will access the data in question again. Of course, due to the halting problem, we can't actually solve that problem, so all garbage collection algorithms approximate it, with the requisite possibility of leaks.


Kernels tend to want to be very specific about how memory is being used. GCs cause the opposite situation.

i.e. these "specific requirements and limitations" you mention have a LOT to do with memory.


Kernels need to be very specific about how other program's memory is being used. And they try and keep their own memory usage low (and predictable) because that's easier to understand and debug and doesn't waste system resources.

Just because there's a GC on the kernel's memory doesn't mean that the GC is somehow now working on all the process' memory that the kernel is running. That's not how the kernel works. The page table just holds a bunch of information about which processes are using which pages, garbage collecting a particular page table entry because the entry wasn't being used anymore wouldn't cause the memory which the entry was pointing to to suddenly be destroyed. And if the page table entry somehow gets to the point where it could be garbage collected that would mean that nothing is referencing it anymore, which would (in a properly written kernel free of bugs!) mean that the process isn't using the memory anymore, so there's no harm done.


Yes, locations in memory, not the kernel's working memory space. i.e. so long as you can have your program loaded after a certain point, write to, and read from, specific locations in memory, you're fine. How the kernel manages its own memory is up to it (for example, one of the tasks which is required to bootstrap into C is establishing a stack so you can call a C method).

EDIT: Real life example of a GC'ed kernel:

https://en.wikipedia.org/wiki/JavaOS


Last time I looked at Nim, I was put off by the lack of algebraic data types and pattern matching. I still can't find anything about them in the manual. Am I missing something, or is this something it just doesn't have?


Instead of ADTs there are object variants in Nim: http://nim-lang.org/manual.html#object-variants

With ADTs:

  data Shape a =
    Rectangle
      { x :: Float
      , y :: Float
      , width :: Float
      , height :: Float
      }
    | Circle
      { x :: Float
      , y :: Float
      , radius :: Float
      }
With Object Variants, note that we don't have to repeat x and y:

  type
    ShapeKind = enum Rectangle, Circle
  
    Shape = object
      x, y: float
  
      case kind: ShapeKind
      of Rectangle: width, height: float
      of Circle: radius: float
The pattern matching in Nim is quite limited. Something like this works:

  let (x, y) = getCoordinates()
But Nim doesn't have (x,y) = (y,x) for example, instead swap can be used.

You could implement your own pattern matching using metaprogramming: http://www.drdobbs.com/open-source/nimrod-a-new-systems-prog...


Maybe it'll be renamed "Nimix", that's a great name!


An idividual drawn to novel immature languages switches their interest as language matures.




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

Search: