Comment by seanw444
1 month ago
My two recommendations are easily Nim and Zig.
If you want something that is essentially just a modernized C, go with Zig. The concept of compile-time programming having the same appearance as runtime programming is very cool in my opinion. My only major complaint at the moment is that duck typing is fairly prevalent. Sometimes function arguments are declared `anytype` and you occasionally have to dive down multiple function calls to figure out what's going on, though that's not too much of a hindrance in practice, in my experience.
My personal favorite language is Nim. Efficient, but simple, memory management (drawing from C++/Rust). You rarely have to think too hard about it, yet making fast programs is not complicated. You can stick to the stack when you want to. The flexibility at compile-time gives you great power (but it requires great responsibility -- easy to abuse in a bad way). The type system is awesome. The only downside for me is the tooling. The LSP needs much optimization, for example.
My issue with Nim is its import system. If you have a function "foo" it's hard to tell where is it imported from. I'm not sure why this bothers me when C is the same... probably because I'm familiar by now which header defines any C function.
Also, I believe high-level compiled languages suffer from the fact that it is very hard to tell which construct is expensive and which is a zero-cost abstraction. Rust has the same issue, but "zero-cost" is a major feature of the language so you don't feel bad using an Iterator, for example, in kernel code. With Nim it is hard to tell.
It makes logical sense to do imports that way when operator overloading exists. Otherwise your custom operators would look like:
Which is very ugly. At that point, we might as well just go with the function name approach that languages like Go take:
I suppose you could change it so operators are imported into the same namespace, and non-operators still require a separate namespace when referred to. But that makes it even more complicated in my opinion. I agree it's less obvious what's coming from where, but I think when your libraries have distinct responsibilities, it usually ends up being pretty straight-forward what function comes from where based on how it's named (if it's written well).
I find the type system in Nim to be pretty poor. It's difficult to reason about what is on the stack vs heap by looking at the business logic and not the types themselves, and also hard to reason about when you do copies vs pointers, since everything is defined on the type itself. I find it to be a bad design decision, I wouldn't build anything large with it.
> The concept of compile-time programming having the same appearance as runtime programming is very cool in my opinion
https://tour.dlang.org/tour/en/gems/compile-time-function-ev...
>The concept of compile-time programming having the same appearance as runtime programming is very cool in my opinion.
You mean, something that Lisp does since the early 1980s?
I didn't say it was novel. It's just not something you see in modern languages.
Should that make it uncool?
> You mean, something that Lisp does since the early 1980s?
Um, no. Debugging a macro in Lisp is a terrible experience while debugging a comptime function in Zig is brain dead simple.
Zig is the first "macro" system I've used that doesn't want to make me blow my brains out when I need to debug it.
Yes, I think that's what they mean.