There's also cases where case sensitivity should matter in naming in my experience, and it's not possible without it.
I'm really curious, do you have any examples? I can't think of any that wouldn't be better served by using the type system.
HN user
There's also cases where case sensitivity should matter in naming in my experience, and it's not possible without it.
I'm really curious, do you have any examples? I can't think of any that wouldn't be better served by using the type system.
Oh yes, just like you can never find anything in Google because it's case insensitive.
Case sensitivity is worse in every way IMO. Look at Nim's implementation of UFCS, which means `foo(bar) == bar.foo`. This means only one possible `foo` that takes type of `bar` can be invoked. There's no ambiguity if `foo` is a method, a proc, or a field - if there were, you'd get a compile time error.
Similarly, case insensitivity, of course, means `foo == fOO == fOo == foO`. Reducing these to one identifier means less ambiguity to the programmer and encourages either using sensible names that can't easily be confused, and/or unambiguous mechanisms like the type system designed for this purpose.
Say you have a constant for 'light enabled' Instead of naming it `lEn`, it's better to use `type LightState = enum disabled, enabled` and write `light.set enabled` for so many reasons. Same goes for pretty much everything. It's worth noting as well that the language is very good at resolving overloads by type, so if you have your own object and create a `len` for it, it's not going to get confused with the string `len` or even a `lEn` constant. Even if you have a `lEn` and `len` that are used in the same context with the same types (why though), you can qualify it anyway with `module1.lEn` or `module2.len`.
The language has so many easy tools to make things more explicit and easier to read at the same time. Ultimately, case sensitivity only really ends up encouraging bad naming practice without adding anything back.
I'm a full time Nim developer and have been for over half a decade. It's completely spoiled me for other languages, it is just ridiculously productive. Rather than an ah-ha moment, it was more a gradual transition from "where's the catch?" to "there's no catch!".
The simple answer is this: anything I want to do, I can do in Nim easier than other languages, while also having direct access to C/C++/JS ecosystems as well.
Productivity:
1. I write pseudocode, it compiles fast and runs at C speeds. Programming is fun again!
2. No `::!>><>^<<@{};` cruft everywhere. Write as you want to read, even spanning multiple lines is clean and without separators.
3. Procedural: only data types and code. No need for OOP/Trait abstractions to be forced into everything (it's there if you must).
4. UFCS and overloading by parameter types make everything naturally extendable: `len("string")` can also be written `"string".len` or `len "string"` - you don't have to remember which way round it goes, and 99% of the organisational benefit of OOP emerges from this lexing rule. A compile time error if you use the same name and parameter types, so no ambiguity.
5. Sensible defaults everywhere: type inference, stack allocated and pre-zero'd variables by default, extend with GC/manual management by type (`type HeapInt = ref int`), GC is deterministic with scope based destructors and move semantics as an optimisation rather than a straight jacket, detailed opt-in control down to assembly as you wish.
6. Arguably the best compile time support of any language.
7. AST procedural metaprogramming is a core language feature. It's hard to express how powerful and well integrated this is. You can just chuck code around and recombine it effortlessly. Whether it's simple DRY replacements, automating serialisation from types, custom DSLs at your convenience, or even generating entire frameworks at compile time from data, you effectively have another dimension to programming. I can't go back to flatland, now.
8. Flexible static typing that's as strict (`type specialId = distinct int`) or generic as you want, with concepts matching any statement against a type. You can also calculate or compose types from static values which is really nice.
9. Low overhead and high control makes it great for embedded: https://github.com/EmbeddedNim
10. Fantastic FFI that can even use C++ templates, along with loads of converters/wrappers like c2nim, futhark, pas2nim that add even more sugar to FFI interop.
Portability and glue:
- Single portable executable output.
- Compiles to C89/C99, which covers basically every piece of hardware.
- Compiles to C++ so you have C++ ABI compatibility.
- Compiles to JavaScript.
- Compiles to ObjC.
- Compiles to LLVM.
- Excellent Python interop (see: Nimpy).
- Libraries for interfacing with C# and .Net.
It's less that "Reddit uses NIM" but rather one guy at Reddit uses NIM to do work.
They are the same thing. No one is saying Reddit bases their entire infrastructure on Nim. The point is just that the language is being successfully used in a business context.
This might not be important to you if you're not a user of the language, but for people that are, it is significant for two reasons: 1) it shows it's 'good enough' to do analytics & data processing for a large, data heavy company and 2) creates confidence/proof it can be used in a business context.
As for technical debt, I doubt that's the case. The post mentions internal tools and processing, these kind of things are often not subject to high developer churn.
Besides, Nim has a pretty easy to read syntax that's similar to Python. People who have talked about onboarding developers to the language (such as https://youtu.be/5wljNaPkU7M?t=586) say it has been fairly straightforward to train for if desired.
Yeah definitely. Unlike Python though it's fast and light on memory, so once you have a prototype it's often already performant enough to be developed into a final product. Things like that save a lot of time and money in the long run.
TypeScript is specifically a good choice because it has been built with the browser and the backend in mind.
Sure, if your only use case is web front/back end uniformity.
My use case covers web front and back end, but also custom embedded, HPC, ML, simulations, and a lot of other stuff that is simply not viable with TS/JS or WASM. This language lets me share abstractions and reuse code across all this stuff and use C/C++/JS/Python ecosystems directly.
All the above are possible with just JS. Yes, you can even run JS on a microcontroller: https://www.espruino.com/
The V8 JS/WASM engine, however, is written in C++ for good reason. TS/JS/WASM/web is built on people counting ticks and aligning bytes to give even the floppiest callback spaghetti of frameworks the overhead to be responsive as abstractions increase.
I have learned over the years that what actually drives the modern world is pragmatism.
I agree, except I think it's more pragmatic to have one language that can service any industry. TS/JS ain't it, from my perspective.
reimplement hundreds of libraries from scratch because the ecosystem doesn't exist then you are losing far too much time
Why would you reimplement libraries from scratch instead of using FFI? FFI is one of the many, apparently unrecognised, 'killer features'. Specifically killer is FFI with ABI compatibility to C++. The pragmatic approach is always to stand on the shoulders of giants and FFI with existing libraries for the reasons you listed.
I'm surprised to hear you say that to be honest as you've used the language for years. Is there an experience behind it?
You're better off losing some performance and picking the established language, so that you actually ship your project within a couple of months instead of a couple of years.
Well, I don't disagree with the sentiment, but this is more of a commentary on the job market/cost of business than capability.
Again, though, it really depends what you're doing. Chucking out a web app for an internal tool and distributed websites have different requirements.
Nim has been going now for 15 years and has thus far failed to reach critical mass. With each year gaining that critical mass becomes less and less likely.
This is a strange perspective to me. Programming languages aren't food left out of the fridge, they're tools to do a job.
Python was made in the 1980s and took decades to even be known about. Why did people start using Python? Because it was readable, convenient, and very productive. Why did it become popular? Because people used it more...
What you call 'critical mass' is just a visible hiring pool.
Companies need confidence to find or train people in the language, and developers need confidence they can pay their bills by learning it. Confidence is increased when people hear of business use, say, if FAANG started a some minor project with it, and it feeds back.
In the general case I think you underestimate how performant JavaScript JITs are these days.
No, I'm well aware of JS and WASM performance, internals, and pitfalls.
For 99.9% of use cases the performance is enough.
Again, it depends on your perspective and requirements. Statistically, PHP is enough for most people so why use TypeScript? At the edges of the resources bell curve however the details do matter. It's not enough to say that for those concerns you must use different language and potentially a whole different set of semantics, intricacies, libraries and workflows.
I think we need to think bigger. Embedded is far too small a niche to be considered to be Nim's killer feature
That wasn't my point, and ironically you might be underestimating how much minimising power and resource usage drives much of the modern world. Memory/storage + CPU ticks == money.
My point is that a language that can act as a universal tool without sacrificing efficiency, productivity, or extensibility (via AST macros), is itself a 'killer feature'. Nim hits that sweet spot of being easy to read and write as well as 'close to the metal'. It lets me target JS, embedded, and natively interface with C and C++ ABI. That gives me a lot of options with one language. There are other languages that have some of these traits, but not all together in a nice package IMO.
especially when this space is already dominated by Rust
You mean C?
you can build the backend and frontend in one language (TypeScript)
Sure, from the uniformity side, but what about performance?
It's not just latency performance either, it's resource performance and overheads, such as with embedded servers.
with TypeScript this killer feature is already realised
It really depends on your requirements whether this is true. Personally, a systems language I can use for web stuff is more useful and has a wider scope to me than running JavaScript or WASM in a systems role, even though it is technically possible to do so.
Only if you consider the web side.
Backend and frontend in the same language is a massive feature for many reasons, not least performance and uniformity. Adding TypeScript is increasing interface friction and complexity, instead of simplifying things.
Of course, right now this may or may not be easier/harder/feasible with just Nim depending on the project, and many more people know TypeScript, but the ideal of one language > many languages is a worthy one.
the real killer feature to me is the javascript target
Agree, this is amazing because you can share code and data structures between front and backend (for example: https://github.com/karaxnim/karax).
Also, it's really nice having high level stuff like metaprogramming and static typing spanning both targets. Things like reading a spec file and generating statically checked APIs for server/client is straightforward, which opens up a lot of possibilities.
100% my experience too. GC in Nim is a rounding error at worst and is often statically elided, anyway. Performance is almost entirely about memory access patterns and you have the same access to this as C.
In other words, performance in Nim is entirely down to the algorithm used, which is a nice place to be.
you often have quasi-singleton classes called "FooManager", then a single instance called "foo_manager".
Like this:
type FooManager = object
var foo_manager: FooManager
> Now... are these colliding? Or not, because the first letter _is_ case-sensitive?Well as you correctly reason, the first letter's case distinguishes them.
Convention in the language is for types to start with a capital letter and instances start with a lower case letter.
What does "fooManager" map to then?
It maps to the `foo_manager` instance because underscores are ignored, they're just for you. By the way, underscores are exceedingly rarely used in Nim code because they're not semantically significant, why bother.
Still, if you like you can use them in your code, and others can choose not to as they want. Clashing identifiers are a compile error so no worries.
Searching has never been a problem for me in years of using Nim but there is `nimgrep` bundled for style insensitive search. Also I'm no RE wiz, but seems like RE might help if it came down it.
Just to stress myself as an anecdote though, I have years in the language and it hasn't come up yet for me, and I've never needed to use `nimgrep`.
other people's code can, refer to the same thing by different names
They can but the compiler will just tell you it's ambiguous and to qualify it. Also bear in mind Nim has very strong static typing, so for things to clash they also have to have exactly the same type, otherwise no worries.
You can even rename symbols on import or force qualification for all symbols, but I've never needed to do either in years of heavily using the language.
the automatic conversion from camel-case to snake-case... very much so.
If an identifier clashes, you get a compile time ambiguity error. It means `is_OK` and `isOk` will report ambiguous identifiers and force you to qualify it or rename it.
This also helps further reduce ambiguity indirectly by forcing sensible naming.
Nim's super power is being ridiculously productive (at least for me). Hack stuff out like a Python script, yet it runs really fast and is a tiny self contained executable, so you can just use it as is and move on to the next task. If you want manual memory management, that's easy too. Want to use a C/C++ library? No worries you have ABI compatibility. As you mention compiling to JS lets you use it as a web language and share code and types between front and back end.
Then you can automate code generation with the sublime macros, which are just standard Nim code to create Nim code. No new syntax or special rules required - any Nim code can be run at compile time or run time, so you can use standard/3rd party libraries at compile time to write macros and give the user a slick syntax whilst removing boilerplate.
I really miss languages without straight forward metaprogramming after using Nim. It's something that multiplies the power of a language, rather than just adds to it.
Nim - https://nim-lang.org/
Lets you mix and match other libraries with their native ABI as it compiles to C, C++, ObjC and JS + has excellent FFI.
We don't want to automatically convert between `int` and `float` because there's a loss of information, since floats aren't able to represent integers precisely.
However, we don't need to specify types until the point of conversion:
let a = 1
let b = a.float
> Python's behavior (though correct to spec) is arguably worseYeah that is not ideal. Looking at the code it seems logical at first glance to expect that `b` would be a `float`. In this case, the type hints are deceptive. Still, it's not as bad as JavaScript which doesn't even have an integer type! Just in case you haven't seen this classic: https://www.destroyallsoftware.com/talks/wat
Another gotcha I hit in Python is the scoping of for loops, e.g.,https://stackoverflow.com/questions/3611760/scoping-in-pytho...
Python takes a very non-obvious position on this from my perspective.
Ultimately, all these things are about the balance of correctness versus productivity.
I don't want to be writing types everywhere when it's "obvious" to me what's going on, yet I want my idea of obvious confirmed by the language. At the other end of the scale I don't want to have to annotate the lifetime of every bit of memory to formally prove some single use script. The vast majority of the time a GC is fine, but there are times I want to manually manage things without it being a huge burden.
Python makes a few choices that seem to be good for productivity but end up making things more complicated as projects grow. For me, being able to redefine variables in the same scope is an example of ease of use at the cost of clarity. Another is having to be careful of not only what you import, but the order you import, as rather than raise an ambiguity error the language just silently overwrites function definitions.
Having said that, as you mention, good development practices defend against these issues. It's not a bad language. Personally, after many years of experience with Nim I can't really think of any technical reason to use Python when I get the same immediate productivity combined with a static type checking and the same performance as Rust and C++ (also no GIL). Plus the language can output to C, C++, ObjC and JavaScript so not only can I use libraries in those languages directly, and use the same language for frontend and backend, but (excluding JS) I get small, self contained executables that are easily distributable - another unfortunate pain point with Python.
For everything else, I can directly use Python from Nim and visa versa with Nimpy: https://github.com/yglukhov/nimpy. This is particularly useful if you have some slow Python code bottlenecking production, since the similar syntax makes it relatively straightforward to port over and use the resultant compiled executable within the larger Python code base.
Perhaps ironically, as it stands the most compelling reason not use Nim isn't technical: it's that it's not a well known language yet so it can be a hard sell to employers who want a) to hire developers with experience from a large pool, and b) want to know that a language is well supported and tested. Luckily, it's fairly quick to onboard people thanks to the familiar syntax, and the multiple compile targets make it able to utilise the C/C++/Python ecosystems natively. Arguably the smaller community means companies can have more influence and steer language development. Still this is, in my experience, a not insignificant issue, at least for the time being.
Oh, just to add that
let b: uint = uint(a)
# can be written as:
let b = uint(a)
The type is inferred from the right hand side during assignment. The only reason I wrote this let b: uint = a
is because in my example `a` was an `int`, so let b = a
Would infer an `int` type for `b`, which compiles fine, and doesn't show the type mismatch I wanted to present.So why can't Nim infer from `let b: uint = a`
It "can", but it's a design decision not to by default because mixing `uint` and `int` is usually a bad idea.
This is telling the compiler you want to add an `int` that represents (say) 63 bits of data with a +/- sign bit to a `uint` that doesn't have a sign bit. If `a = -1` then `b = uint(a)` leaves `b == 18446744073709551615`. Is that expected? Is it a bad idea? Yes. So, the explicit casting is "getting in your way" deliberately so you don't make these mistakes. If `a` is a `uint`, it can't be set to `-1`, and adding them is freely allowed.
Incidentally `uint` shouldn't be used for other reasons too, for instance unsigned integers wrap around on overflow, whereas integers raise overflow errors. The freedom of mixing types like this are why languages like C have so many footguns.
In short, explicit is better than implicit when data semantics are different. When the semantics are the same, like with two `int` values, there's no need to do this extra step.
You could create a converter to automatically convert between these types, but you should know what you're doing; the compiler is trying to save you from surprises. For `int`/`float`, there is the lenientops module: https://nim-lang.org/docs/lenientops.html. This has to be deliberately imported so you're making a conscious choice to allow mixing these types.
don't you get tired of typing (and reading) `uint` twice in the latter setting?
Well, no because I wouldn't be writing this code. This example is purely to show how the typing system lets you write pythonesque code with inferred typing for sensible things, and ensures you're explicit for less sensible things.
For just `int`, there's no need to coerce types:
var
a = 1
b = a + 2
intro = "My name is "
name = "Foo"
greeting = ""
b *= 10
# Error: type mismatch: can't concatenate a string with the `b` int.
# greeting = intro & name & " and I am " & b & " years old"
# The `$` operator converts the `b` int to a string.
greeting = intro & name & " and I am " & $b & " years old"
# If we wanted, we could allow this with a proc:
proc `&`(s: string, b: int): string = s & $b
# Now this works.
greeting = intro & name & " and I am " & b & " years old"
echo greeting # "My name is Foo and I am 30 years old"
# Normally, however, we'd probably be using the built in strformat.
# Incidentally, this is similar to the printf macro mentioned in the article.
import strformat
echo &"My name is {name} and I am {b} years old"not slowing one's work
Put back into context, your reply makes sense as these popular libraries are pretty battle tested. Having said that, it is a valid point that type hints being voluntary means they can only be relied upon with discipled developers and for code you control. Of course, the same point could be made for any code you can't control, especially if the library is written in a weakly typed language like C (or JS).
I just don't see its absence as the crippling dealbreaker
My genuine question would be: what does dynamic typing offer over static typing? Verbosity would be my expectation, but that only really seems to apply without type inference. The other advantage often mentioned is that it's faster to iterate. Both of these don't seem particularly compelling (or even true) to me, but I'm probably biased as I've spent all of my career working with static typing, aside from a few projects with Python and JS.
if there are languages besides JS that you feel get their type system "just right", I'd be curious as to what they are
This is use case dependent, of course. Personally I get on well with Nim's (https://nim-lang.org/) type system: https://nim-lang.org/docs/manual.html#types. It's certainly not perfect, but it lets me write code that evokes a similar 'pseudocode' feel as Python and gets out of my way, whilst being compile time bound and very strict (the C-like run time performance doesn't hurt, too). It can be written much as you'd write type hinted Python, but it's strictness is sensible.
For example, you can write `var a = 1.5; a += 1` because `1` can be implicitly converted to a float here, but `var a = 1; a += 1.5` won't compile because int and float aren't directly compatible - you'd need to type cast with something like `a += int(1.5)`, which makes it obvious something weird is happening.
Similarly `let a = 1; let b: uint = a` will not compile because `int` and `uint` aren't compatible (you'd need to use `uint(a)`). You can however write `let b: uint = 1` as the type can be implicitly converted. You can see/play with this online here: https://play.nim-lang.org/#ix=3MRD
This kind of strict typing can save a lot of head scratching issues if you're doing low level work, but it also just validates what you're doing is sensible without the cognitive overhead or syntactic noise that comes from something like Rust (Nim uses implicit lifetimes for performance and threading, rather than as a constraint).
Compared to Python, Nim won't let you silently overwrite things by redefining them, and raises a compile time error if two functions with the same name ambiguously use the same types. However, it has function overloading based on types, which helps in writing statically checked APIs that are type driven rather than name driven.
One of my favourite features is distinct types, which allow you to model different things that are all the same underlying type:
type
DataId = distinct int
KG = distinct int
Data = object
age: Natural # Natural is a positive only integer.
weight: KG
var data: seq[Data]
proc newData: DataId =
data.setLen data.len + 1
DataId(data.high) # Return the new index as our distinct type.
proc update(id: DataId, age: Natural, weight: KG) =
data[id.int] = Data(age: age, weight: weight)
let id = newData()
id.update(50, 50.KG) # Works.
50.update(50, 50.KG) # Type mismatch got int but expected DataId.
id.update(50, 50) # Type mismatch got int but expected KG.
id += 1 # Type mismatch += isn't defined for DataId.
As you can imagine, this can save a lot of easy to make accidents from happening but also enriches simple integers to serve other purposes. In the case of modelling currencies (e.g., https://nim-lang.org/docs/manual.html#types-distinct-type) it can prevent costly mistakes, but you can `distinct` any type. Beyond that there's structural generics, typeclasses, metaprogramming, and all that good stuff. All this to say, personally I value strict static typing, but don't like boilerplate. IMHO, typing should give you more modelling options whilst checking your work for you, without getting in your way.Compare with dynamic languages (or structural typing, Go etc) that only care that things "quack like a duck".
Go is a statically typed language. Unless you're referring to interfaces at run time?
I'm saying the function has 65 input parameters of all these specific types and nobody can call it now without providing them all. What a pain! Huge amount of extra code, refactoring, unit testing, because of this.
I've seen this "cambrian explosion of micro-types" argument before on HN but I think it comes from a misunderstanding about what you actually do in a static language. No one's creating types for every combination of parameters.
Either you'd pass the two arguments directly (`a, b: int` or whatnot), or you'd pass the Order type and just use the bits you need.
If you have multiple Order types, you'd use generics or something like it to get duck typing. If you used a field that wasn't there, you'd get a compile error.
The reality is the code would look pretty much the same in both static or dynamic languages.
just doesn't slow one's work down very frequently in daily practice.
Well, maybe you don't feel it slows you down, but it is manual work you must do to get a reliable product only because of dynamic typing. Not only that, but you have to then refer to these docs to check you're not creating a type calamity at some nebulous point down the run time road. Static languages just won't let you mess that up, and often have utilities to generate this documentation for you at no effort.
I just don't buy the suggestion that static typing magically solves a huge set of problems
Static typing really does "magically" solve a whole class of problems without any negative tradeoffs, assuming the language has decent type inference.
Not all problems, but a specific class of them that you should do extra work to guard against in dynamic languages. Whether that is extra documentation that has to be reliably updated and checked, or run time code to check the types are what you expect at the point of use.
Take for example JavaScript, where typing is not only dynamic, but weak. Numbers in particular can be quite spicy when mixed with strings as I'm sure you know. Strong, static typing forces you to be explicit in these cases and so removes this problem entirely.
By the way, no one's saying anyone is a simpleton. The reality is our field is wide and varied, and different experiences are valid.
Dynamic languages can do some things that static languages can't. For example, you can return completely different types from different execution paths in the same function.
This has been something that has confused me when reading Python, but it does make it easier for stuff like tree parsing. In a static language you need to specify some variant mechanism that knows all data possibilities ahead of time to allow this. From my perspective the dynamic typing trade off isn't worth these bits of 'free' run time flexibility, but YMMV! It really depends what arena you're working in and what you're doing.
In Nim you can even convert JSON to static types! https://nim-lang.org/docs/json.html#to%2CJsonNode%2Ctypedesc...
Now you get type checking on JSON at compile time :)
I think the message is more nuanced
I thought it was more nuanced too as they were explaining how integer types can be derived, until I finished the article, and they really did just seem to be complaining that there's a mismatch between compile time and run time.
Dynamic types don't really solve the problems they mention as far as I can tell either (perhaps I am misunderstanding), they just don't provide any guarantees at all and so "work" in the loosest sense.
otherwise wouldn't lisp with its homoiconicity and compile time macros fit the bill perfectly?
That's a good point, I do wonder why they didn't mention Lisp at all.
we don't have a solution yet
What they want to do with print can, as far as I can see, be implemented in Nim easily in a standard, imperative form, without any declarative shenanigans. Indeed, it is implemented as part of the stdlib here: https://github.com/nim-lang/Nim/blob/ce44cf03cc4a78741c423b2...
Of course, that implementation is more complex than the one in the article because it handles a lot more (e.g., formatting and so on).
At the end of the day, it's really a capability mismatch at the language level and the author even states this:
Programming languages ought to be rethought.
I'd argue that Nim has been 'rethought' specifically to address the issues they mention. The language was built with extension in mind, and whilst the author states that macros are a bad thing, I get the impression this is because most languages implement them as tacked on substitution mechanisms (C/C++/Rust/D), and/or are declarative rather than "simple" imperative processes. IMHO, most people want to write general code for compile time work (like Zig), not learn a new sub-language. The author states this as well.
Nim has a VM for running the language at compile time so you can do whatever you want, including the recursive type decomposition (this lib isn't implementing Peano arithmetic but multiprecision stack based bignums): https://github.com/status-im/nim-stint and specifically here: https://github.com/status-im/nim-stint/blob/ddfa6c608a6c2a84...
func zero*[bits: static[int]](T: typedesc[Stuint[bits] or Stint[bits]]): T {.inline.} =
## Returns the zero of the input type
discard
func one*[bits: static[int]](T: typedesc[Stuint[bits]]): T {.inline.} =
## Returns the one of the input type
result.data = one(type result.data)
It also has 'real' macros that aren't substitutions but work on the core AST directly, can inspect types at compile time, and is a system language but also high level. It seems to solve their problems, but of course, they simply might not have used or even heard of it.Thanks for your reply.
It's interesting to know what the perceived weaknesses and strengths are. Nim does have pattern matching but it's rarely used, whereas it seems to be used a lot in Rust (probably because of the prominence of enums). Nim has static lifetime management, but it's mainly used for eliding and thread safety (for now). Traits are an interesting feature, and make a good example of why I'm so bullish on Nim: someone has already replicated them with a macro: https://github.com/haxscramper/nimtraits
The popularity critical mass thing is mainly getting eyes on the language, but I think Nim has a slight advantage in that it's incredibly cooperative with its compile targets and FFI. Like Python, it's great for good glue code and 'scripting' without the performance penalty, and I hope that helps it meld into people's toolboxes over time.
The syntax is what attracts me to Nim too.
Why use Rust over Nim in this case?
Is it Rust's static lifetime constraints? Is there some feature you would miss if you used Nim instead? Or would you prefer to use Rust because more organisations are using it?
I'm also not a fan of canonicalizing variables so you can write them any way you want to, since it harms grepping and the consistency of code.
Perhaps surprisingly, the opposite is true. You can enforce a style and a library that uses another style doesn't pollute yours and mix it all over the place.
There's also `nimgrep` (never needed this in >5 years on this language), or `nimpretty` to normalise styles.
someday stop having to use C++
Nim can compile to C++ so you get ABI compatibility, and control over performance should be just as fine tuned. Nim should be able to run at the same speed as C/C++ since they both offer the same level of hardware access and portability. Depending on your needs it should be possible.
code tends to be both surprisingly terse while remaining readable, and fast to compile and run.
This is one of Nim's most powerful features. Easy to read productivity. Prototypes end up being good enough, and it's got the teeth when you need them.
The language default is 'fast and safe', all the fancy stuff is opt in when you need it. E.g., variables are put on the stack by default, heap GC is attached to the type rather than instance (static lifetime + RC/optional cycle collector). Variables are initialised to zero, but there's `{.noInit.}`, access raw pointers but with strong typing and construct/destroy hooks, and so on.
Things like this: https://nim-lang.org/docs/manual.html#pragmas-linearscanend-... and the below computed goto pragma add even more control should you need it.
Then there's the god-tier metaprogramming that lets you have optimisations that would be impossibly onerous to write, let alone maintain, manually, and give them the sweetest syntax you like. Full compile time VM in the native language, and type safe AST macros. Just standard Nim code, processing syntax trees with the standard library, say, outputting a framework from some data in a file, or providing a deep learning DSL that generates code for optimised matrix operations. It's like going from 1D to 2D.
Thanks for making these, I actually had no idea these existed! I don't "need" them now but seeing these gives me ideas for projects and makes future things easier.
I wish discovery of community libraries was higher, I'm constantly discovering libraries that do amazing things 'hidden' away. I know there's https://nimble.directory/ and https://github.com/xflywind/awesome-nim but most of the time I end up using a search engine for something specific only if I think of it.