j-james
Writing an equivalent program is a bit weird because: 1) Nim does not distinguish between owned and borrowed types in the parameters (except wrt. lent which is bugged and only for optimizations), 2) Nim copies all structures smaller than $THRESHOLD regardless (the threshold is only slightly larger than a pointer but definitely includes all integer types - it's somewhere in the manual) and 3) similarly, not having a way to explicitly return borrows cuts out much of the complexity of lifetimes regardless, since it'll just fall back on reference counting. The TL;DR here though is no, unless I'm mistaken, Nim will fall back on reference counting here (were points 1 and 2 changed).
For clarity as to Nim's memory model: it can be thought of as ownership-optimized reference counting. It's basically the same model as Koka (a research language from Microsoft). If you want to learn more about it, because it is very neat and an exceptionally good tradeoff between performance/ease of use/determinism IMO, I would suggest reading the papers on Perseus as the Nim implementation is not very well-documented. (IIRC the main difference between Koka and Nim's implementation is that Nim frees at the end of scope while Koka frees at the point of last use.)
The C FFI Nim library lineage goes c2nim --> nimterop --> something i forgot --> futhark.
It is perhaps notable that the nimskull project has a Code of Conduct (and a rather good one at that). I think it is indicative of serious intention to make a welcoming community, and I don't think that contributing members having had in the past what really struck me as personal grievances turning into open-air malice undermines that: particularly, such behavior would fall under the Code of Conduct itself. But again, I have not seen such behavior - quite the opposite! - outside of what happened between you two.
dom96 eventually had a falling out with the Nim BDFL too (which I'm surprised did not happen earlier: the BDFL is... brusque, charitably) and so has been inactive in either Nim community because, well, obviously. But: in all my years involved with Nim, I have seen the nimskull developers to be pretty consistently great to work with? I think they handle things professionally, are not rude, and treat other's work with care (you will maybe notice they have a code of conduct exposing an explicit intent to do so). Which makes what happened between them and dom96 all the stranger in my eyes, but I was less active back then, and there is certainly context I am missing.
tl;dr meh
(I've heard plenty of complains about Rust syntax, so I'm less so interested in that than what C++ syntax gets right. Unless it's just a familiarity thing...)
It's in the same category as Swift, yes, but much improved: Swift does not do ownership analysis to get rid of counts (though I've heard they're looking at alternative region-based approaches), and their counts across threads are always atomic (and thus slow).
Reference counting has traditionally led to worse performance than tracing. So even though I get the desire to think of it as separate because it's just transparently replacing your allocator / deallocator with one that does a little bit more instead of having a whole separate tracing collector program, I'd still probably refer to both tracing and reference counting as "garbage collection", and then refer to them + ownership systems (+ regions + everything else new) as "memory management techniques".
The overlap in implementation techniques between tracing and reference counting is interesting. You might enjoy this paper: https://dl.acm.org/doi/10.1145/1028976.1028982
The reason Koka's GC is interesting despite being based on reference counting is that its ownership system eliminates most of these reference checks at compile time - and additionally can tell whether to use atomic RC (slow but threadsafe for shared data) or non-atomic RC (fast but only threadsafe when data is moved across threads, not shared). This ownership analysis is very similar to what some other languages like Nim do (except Nim differs in not allowing atomic RC at all).
The other strange terminology that is occasionally tossed around in Koka documentation is "garbage free": Koka takes this to mean that at any given point in the program, there is no memory waiting to be freed. This is because the ownership analysis lets the compiler know exactly where the last use (or possible last use) is and insert destructors accordingly. All of that has made Koka's GC algorithm fast and low-overhead enough that it's competitive with state-of-the-art tracing GCs (specifically, OCaml's GC). I haven't seen benchmarks comparing it to manual memory management or strict ownership systems but that's not terribly the point - manual memory management is unsafe and strict ownership is complicated + inexpressive on occasion. Koka's system might just be the best you can get, with those tradeoffs in mind.
Anyway, this doesn't answer your question at all. Sorry. I hope it's interesting, though.
An ABI for languages with a proper type system seems fantastic. Swift, Rust, Nim, D all share very similar type systems (and memory management systems) and it would be very cool to see what kinds of interop easy dynamic linking would allow.
Eh, https://github.com/arnetheduck/nim-results and associated syntax from https://github.com/codex-storage/questionable would beg to disagree. Nim's stdlib does not have productive and well-fitting errors because it suffers from inertia and started far before the robust wonders of recoverable error handling via errors-as-types entered the mainstream with Rust and were refined with Swift (IMO). Option/Result types are fantastic and I do so wish the standard library used them: but it's nothing a (very large) wrapper couldn't provide, I suppose.
I do strongly think that other languages are greatly missing out on UFCS and I miss it dearly whenever I go to write Python or anything else. I'm not quite sure how you think UFCS would make it impossible to have good error handling? Rust also has (limited, unfortunately) UFCS and syntax around error handling does not suffer because of it. If by errors-as-values you mean Go-style error handling, I quite despise it - I think any benefits of the approach are far offset by the verbosity, quite similarly to Java's checked exceptions.
(in general concerns surrounding performance of errors surprise me - they're errors! they shouldn't be hit often! but if they are, you can certainly avoid such performance hits in nim.)
That seems accurate. Dealing with raw pointers as one does in Odin or Zig is very much de-emphasized in favour of dealing with safe references, and a lot of effort is put into optimizing out all the overhead of those reference checks (hence ARC/ORC) and writing code to evade them. The manual memory management features of Nim are there for flexibility and fallbacks and are not really the main way to write code: even for embedded. The stuff that Zig (and Odin?) do surrounding allocators and alignment, and constructs for slightly-safer pointers, are really very interesting yet are most helpful if you are indeed working with pointers and worrying about offsets: which you usually aren't in Nim.
I am curious as to what you mean about comptime, though. I have gotten the impression that equivalent constructs in Nim are more powerful. You have `static` blocks and parameters, `const` expressions, `when` conditionals, and then also both templates and typed macros operating on the AST (before or after semantic checking)... `when` even provides for type-checking functions with varying return types (well, monomorphized to one type) via `: auto` or the `: int | bool | ...` syntax.
I will also defend "naked imports" as a feature that works very well with the rest of the language: functions are disambiguated by signature and not just name and so conflicts scarcely occur (and simply force qualification when they do). And, this allows for the use of uniform function call syntax - being able to call arbitrary functions as "methods" on their first parameter. This is incredibly useful and allows for chaining function calls via the dot operator, among other things. Besides, if you really want you can `from module import nil` and enforce full qualification.
Interest in proper structural pattern matching sparked back up again recently and some complementary RFCs were proposed: https://github.com/nim-lang/RFCs/issues/525 and https://github.com/nim-lang/RFCs/issues/527.
- npeg lets you write PEGs inline in almost normal notation: https://github.com/zevv/npeg
- owlkettle is a declarative macro-oriented library for GTK: https://github.com/can-lehmann/owlkettle
- ratel is a framework for embedded programming: https://github.com/PMunch/ratel
- futhark provides for much more automatic C interop: https://github.com/PMunch/futhark
- nimpy allows calling Python code from Nim and vice versa: https://github.com/yglukhov/nimpy
- questionable provides a lot of syntax sugar surrounding Option/Result types: https://github.com/codex-storage/questionable
- nlvm is an unofficial LLVM backend: https://github.com/arnetheduck/nlvm
- chronos is an alternative async/await backend: https://github.com/status-im/nim-chronos
- cps allows arbitrary procedure rewriting in continuation passing style: https://github.com/nim-works/cps
A longer list can be found at https://github.com/ringabout/awesome-nim.
Perhaps in the future we'll simply have both and have no reason to debate ;-)
- Tooling is not great. The language server has a tendency to silently crash on occasion, and it's no rust-analyzer to begin with. A tooling rewrite has been delayed behind proper incremental compilation, which has been delayed behind ARC/ORC...
- Interfaces "concepts" are experimental and there are two differing implementations.
- It lacks proper sum types and structural pattern matching in the core language. There are a number of quite good macro-based libraries that provide for this, however: fusion/matching, andreaferretti/patty, beef331/fungus, alaviss/union...
- Optional types are not the standard: the stdlib will throw exceptions. This is more so a personal preference than anything.
Nim also has support for `ctypes` and compiles to C as its main target: yet though its interop is powerful it lacks in ergonomics, formerly you had to manually wrap every function you wished to use and this was only recently fixed by a macro-heavy external library.
I'm wondering what Zig does because IMO even if you have an excellent technical solution getting people to actually use it alongside C is hard, it has to be seamlessly seamless. Nim's C interop is rarely used outside of wrappers and it even more rarely is used to produce C libraries (though perhaps that's more a fault of incompatible type systems)
As background, style insensitivity was introduced so that codebases can use a consistent camelCase or snake_case regardless of the style used by upstream libraries.