I think this is all true. Though with regard to your earlier example, it should be noted that Nim, too, has an extraordinarily powerful compile-time programming system: but it takes the form of typed macros and generics (as opposed to Zig's dislike for such abstractions).
HN user
j-james
It will not emit warnings saying it did that. The static analysis is not very transparent. (If you can get the right incantation of flags working to do so and it works, let me know! The last time I did that it was quite bugged.)
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.)
That relies upon the benevolence of corporations to much more of an extent than I am comfortable with. 20 years of social media has convinced me that that's a bad idea. And, I think, it removes much of the benefits of federation: if the only way to sustainably moderate is to rely upon gifts from BlueSky Inc., moderation is going to be necessarily dependent upon them.
I cannot see how BlueSky's moderation system can ever work. Decoupling moderation and hosting means there's no onus to do the moderation that they describe: which makes me think it will be BlueSky Inc., and only other corporations, that have resources to throw employees at a now thankless, Facebook-style moderation job. And instances have to moderate anyway, in order to not host illegal content.
Ah, I was referring to disruptek. I don't know why you two hated each other's guts, mostly due to being less active back then indeed. For what it's worth I didn't think the banning was undeserved: but the interactions you two had always struck me as strangely animus, and not anything like anything I've ever seen between him and anybody else, and certainly not between any other nimskull developers and anyone, ever.
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.
Some context: dom96 here was the BDFL-in-2nd-command of Nim for quite some time. There is personal beef between dom96 and some of the primary nimskull contributors, which mostly involved a lot of name-calling, personal insults, and critiques of leadership and leadership effectiveness, that eventually lead to dom96 banning one of the aforementioned contributors, stalling quite a lot of interesting compiler work and partially sparking nimskull. It's all quite boring stuff.
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
Well, they're not classes, they're interfaces, and so you can implement multiple interfaces for multiple types, and so you have to implement them separately from the type declaration. There's not too much of a way around that, I think.
I'd be a little interested to hear what you like about C++'s syntax! As a non-C++ programmer, I mostly think of the language as a pile of mistakes that kinda had to happen for other languages to learn from them, very much including syntax (ex. types preceding declarations, necessitating `auto` and complicating parsing) - and I actually think the ugliest parts of Rust copy those mistakes (ex. using <> for generics, which are ambiguous with less-than greater-than when used with no whitespace, necessitating the turbofish).
(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...)
Nim is particularly C-like. The automatic memory management is optional and exposes destructors, it exposes pointers and is quite usable on embedded systems (see: Futhark, Ratel), there are a wide variety of design decisions made to ease interop with C... I do not know about D.
Well, it has a runtime. Checking and updating the reference count (more so the latter) is not zero-cost. This runtime is just deterministic. Of course, then do we have the same definition of runtime (I would take it to mean any extra memory or processor overhead at runtime that is not strictly necessary)... naming and consistent naming is an extremely hard problem in computer science.
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 downsides of this approach are unfortunately that it makes wrapping certain low-level libraries an absolute pain in the ass (especially anything to do with keyboards). But overall it's a non-issue, tooling recognizes both styles and you don't notice it.
Heh, neat to see you've got the Perceus paper there too. That is in fact the other part (the "ARC") of Nim's memory management for those unaware - with the only differences being Nim frees memory at the end of scope, rather than last use, and and Perceus maybe might be atomic (do not fully remember. ARC isn't atomic.)
Hmm, a strong type system? Is there any overlap between the Wit IDL and other similar projects like crABI?
Really, I'm quite hopeful for crabi: https://github.com/rust-lang/rust/pull/105586
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.
You can also not really have productive and well-fitting errors-as-values in a language that emphasizes UFCS
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.)
It's not so much a competitor (in performance or clarity) to Odin or Zig as it is a competitor to Go or something
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.
Ones that have not been mentioned so far:
- 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.
I know, I know! I do very much like the "type wrapper" approach more than the "object variant" approach.
Perhaps in the future we'll simply have both and have no reason to debate ;-)
I have a shortlist of pain points:
- 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.
But that's about it. I do like Nim quite a lot.
Yes, an experiment was run a while back, incorporating community-maintained code in a "fusion" repo shipped with the compiler by default. It didn't work very well. Discoverability and maintainability of stdlib-like things is hard.
Right, but I'm curious about the calling-Zig-from-C interop, not the other way around.
Yeah, I read through that: unless I'm missing something I think what I'm curious about is "calling C code from Zig and vice versa" in the to-be-written section.
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)
Can you use Zig painlessly alongside C? Does `zig cc` or an equivalent provide for writing Zig libraries that then can be called by a main C function?
Well, maybe not writing. Occasionally low-level C libraries - especially those that deal with keyboard input - decide to provide identifiers differing only in case... There's a WIP RFC for providing a way to deal with identifiers that need to be verbatium by surrounding them with backticks, though.
Do note that this was written before Nim's new runtime, ARC/ORC, became stable! The author has been working on a companion guide to the new runtime that goes over how it manages to be efficient with reference counting, but it's (very) much a work in progress.
While I love Nim, another reason for Rust's success (aside from its type system, general good language design, and zero-cost memory safety) is that the tooling is ridiculously good, probably the best of any language I've used, sans maybe Java. Nim's tooling is improving but still has a ways to go - I still occasionally get orphaned langserver processes, and while the langserver itself is pretty good, it's nowhere near the level of what rust-analyzer can do. Especially with error messages and (automatic) refactoring.
Not unless the function signatures overlap entirely. That's rare enough that I don't think I've ever? encountered it in several years of writing Nim.
It's alright when it doesn't crash. But it frequently crashes. Tooling is definitely a weakness of Nim (although it's improving).
More specifically: right now auto completion is excellent, implicit type annotations are excellent, error tooltips on save work, broader rename/refactor support is limited/nonexistent, formatting on save is fine, auto imports don't exist (and probably shouldn't given that it's typical to use unqualified namespaces).
Reference and pointer types (annotated with `ref` and `ptr` respectively) are nillable. The compiler can provide null safety guarantees by annotating types with `not nil`: https://nim-lang.github.io/Nim/manual_experimental.html#not-.... So then your example would look like the following, and fail to compile.
type
Foo = ref object
Bar = Foo not nil
var bar: Bar = nil # error: type mismatch
echo bar.a
Discussion about not nil by default is here: https://github.com/nim-lang/Nim/issues/6638