Hacker News new | ask | show | jobs
by netbioserror 1057 days ago
Been happily crunching away at Nim in production. I'm working on what is mainly a data analysis and report generation tool, compiled as a CLI executable that gets called by server scripts.

Nim makes fast, small executables. It has an excellent heterogenous JSON data structure and a good dataframe library. It prefers the stack so strongly that dynamic data structures (sequences and tables, basically its lists and dictionaries) are pointers on the stack to heap data, where the lifetime is managed by the stack frame. I don't think I have any dynamic references anywhere in my program, and don't have to worry about GC at all. The type system is simple, sensible, and guides you to correctness with ease. Nim also defaults to referential transparency; everything is passed immutably by-value unless you opt out. Generics are powerful and work exactly as you expect, no surprises. Universal function call syntax is ridiculously powerful: You can write the equivalents to methods and interfaces on types just by making procedures and functions that take a first parameter of that type; not needing those abstractions greatly simplifies and flattens code structure. It's just procedures and objects (functions and structs) all the way down.

It's been a real joy to work with and reminds me of when I discovered D back in the day, only it's even better. If you imagine native-compiled type-annotated Python where nearly 100% of your code is business logic with no cruft, you're getting close to the Nim experience.

4 comments

> “It prefers the stack so strongly that dynamic data structures (sequences and tables, basically its lists and dictionaries) are pointers on the stack to heap data, where the lifetime is managed by the stack frame.”

Isn’t that the same as a C++ vector or map on stack? They allocate internally as needed, and the whole container is destroyed when it goes out of scope.

It very much is, and the point is, it _used_ to be more like java. Araq basically pulled off a very daring switchover from reference based language system to a value based one.

So now the language can credibly claim the same as c++ - no room left closer to the metal. But it's packaged in a much nicer syntax (imho), and has features like macros which we can expect I'm C++ in maybe 10 years, if we're lucky.

Basically, but it requires no extra syntax. `var some_seq = @[1, 2, 3, 4]` is a stack-managed sequence. That's all there is to it. There's no unwrapping any pointers or boxes or what-not, the type is just `seq[int]`. Put another way, things that have become best practice in C++ are default in Nim with no syntactic noise.
There's no unwrapping any pointers or boxes or what-not

That doesn't happen by default in C++ either.

std::vector<int> some_seq{1, 2, 3, 4, 5, 7};

or even just

     std::vector some_seq{1, 2, 3, 4, 5, 7}; 
nowadays (for a value of nowadays that is 5 years old for GCC and 6 years old for Clang)
Indeed there's no question that Nim is basically following C++'s lead on this. Nim iirc always had constructors and destructors. Final piece of the puzzle is move semantics, and I recall a blog post where Araq came up with something very similar.
yes, Nim has move semantics, but takes care of you more than c++ does. for example, if you use an object that was previously moved, you dont get garbage, the compiler turns the first move into a copy (and tells you)

the relevant docs are here: https://nim-lang.org/docs/destructors.html

Agreed, only being able to put pointers on the stack, no data, would make me think it "prefers the heap".
Dynamic data structures by their nature have to be allocated to the heap. What I mean by "prefers the stack" is that you don't have to make a managed ref and dereference a managed pointer type. You just make a `seq[int]`, use it as a `seq[int]`, and pass it as a `seq[int]`, just like stack data. Behind the scenes, it has a unique scoped pointer with no mental overhead.
Sounds like a vector/array/list in any other language after C++, like Go slice, Java ArrayList, Javascript array, Python list, Rust vec. Is there something I'm missing?
I think they're discussing the lifetime of that heap data, not whether the data is heap allocated.
I don't see them drawing any distinctions from C++ or Rust there either. It really sounds to me like most of their low-level experience is in C, where the contrasts they appear to be drawing really do apply.
it depends on what you mean by 'dynamic' and 'stack'; certainly, outside of nim, you can allocate a list of, say, integers entirely on a stack in any of the following cases:

- the size of the list is known when you create a stack frame, as in c:

    int xs[n] = {0};
- when the list grows, it grows in that subroutine and not some callee, for example using alloca();

- the list is built on a stack that isn't the one you have to pop your return address off of; examples include perl's data stack, ada's secondary stack, forth's operand stack, forth's dictionary, or an mlkit region. in these cases you can even return the dynamically built structure to a caller;

- each new callee adds some fixed number of items to a linked list, such as, in c

    void with_fill(color *c, 
        env *e, 
        void (*cb)(void*, env*), 
        void *userdata)
    {
      env ne = {
        .prop = PROP_FILL_COLOR,
        .val = c,
        .parent = e };
      cb(userdata, &ne);
    }
look ma, no heap
Types are stack allocated by default.

"var data: MyObject" is on the stack. "var arr: array[1000, MyObject]" is allocated on the stack sequentially.

Only dynamic seq or ref types use the heap by default.

So, same as C++.
Same as Rust. Vec and HashMap are stack pointers to the heap-allocated container storage space.
You have convinced me to look in to Nim! Can you speak to the build system(s)? CMake is the bane of my existence.
Another Nim user here. Typically you just build the project by `nim c <myProject.nim>`, since Nim has such a strong macro system a lot of typical build stuff is just done with macros. Of course there's also the default Nimble package manager which allows you to list dependencies and tasks using Nim itself. This means that if you know how to write Nim managing the build system is a breeze.
I did a lot of nim small projects. All of them are on nimble build system: https://github.com/inv2004
Ugh, I just got done spending months fighting CMake before moving back to a position using Nim!

You can also compile C projects with Nim like bearssl [1]. Nim takes care to compile the C files and recompile them when config flags change. It's actually really nice.

1: https://github.com/status-im/nim-bearssl/blob/99fcb3405c55b2...

Gotta be honest here, I use a pretty simple Makefile. I don't have anyone else working on this program, so I can afford simplicity.
It does indeed look like Python!

I hope it gets more popular, seems like a much much easier to use Rust

This sounds great. How is the package management story, and how robust is the ecosystem currently?
You can check out the list of Nim packages at https://nimble.directory
The ecosystem is smaller than some, but wrapping libraries is mostly automatable, and there is tooling to assist interop with python, c, and c++
This provides a good overview of packages for various applications: https://github.com/ringabout/awesome-nim

I think most of them are available via nimble.