I think this might be of interest to readers of this forum:
http://hardcoded.net/articles/pythonistas-irresistible-attraction-rust
Article: Nim has not nil, but it gets things backwards. If the default it to allow nil, then lazy developers are going to allow it, then forget about it, then access it when nil. That is, unless the Nim compiler analyzes the code to ensure that every access to every nullable variable is inside a if x != nil block, but I'd be surprised if it did.
It may change sooner than later https://github.com/nim-lang/Nim/pull/3296
I think the language would be better with not nil as the default. It's difficult for me to assess whether it would be worth the breakage. Probably yes. For better or worse Nim will be compared to Rust and having not nil as default would reduce the impact of one common complaint.
Probably everyone has seen this too
Upvote. It would be preferable to require an explicit "ref Blah or nil" in proc declarations. I realize this is going to break a lot of code, but it seems like a weapon that other language proponents are going to use against Nim.
I don't think it will be too confusing for newcomers if the error message is reasonable, i.e.
"Error: cannot prove argument is not nil. To explicitly allow nil use ref YourType or nil"
What's really annoying in these discussions is that all claim the default nullability is wrong, "a billion dollar mistake" etc etc and yet nobody has a solution for this:
# this should type-check:
var s = newSeq[ref Foo not nil](100)
for i in 0..<50: s[i] = newFoo()
for i in 50..<100: s[i] = newBar()
(Nim currently allows it with no static checking whatsoever. Maybe that's actually a very good compromise?)
That's not some edge case you can hand wave away. Preallocation and good handling of arrays are absolutely essential for systems programming.
The way I see it, the big reason for this push for proving things to be non-null is because dereferencing a null-pointer is undefined behavior (in C/C++), and making it defined behavior would negatively (and greatly, in some cases) impact performance.
There are two main options:
- Make not nil default, thus enforcing compile-time checks by default.
- Define behavior for dereferencing and handling null references, and add the appropriate checks in the backend code.
Both these choices have their downsides. Making not nil the default has the potential to cause severe breakage with existing code if not handled appropriately, while adding checks in the backend code for null references will have a performance impact (although, perhaps the checks could be omitted for known valid pointers). Of course, either of these options would have to be on by default, and the latter option would have to be on even with -d:release.
How does Rust handle the cases with arrays and null pointers?
You could provide initialization function or construct for sequences/arrays, along the lines of newSeqWith, that would take a procedural argument to fill in the elements.
There are certainly a lot of cases in low level systems programming where you want nullability to be the default. Maybe an unsafe block a-la Rust (the name is not great IMO, 'trusted' is better) where you program like in C?
maybe Nim should implement them as well?
Maybe you should consider we know how other languages do it.
Alisaxy: Options are a very good alternative to null-pointers.
Observe that a nullable type T literally is an option type via T = T not nil | nil.
You can indeed handle them just like option types, e.g. what Eiffel does. However, an alternative approach (as I outlined above) is simply to make it easy to prove that a variable cannot assume the value nil; this would only require that variables must be initialized explicitly.
And as I noted above, option types do not solve the problem for incremental or partial initialization of state and can actually make it worse.
Alsiaxy: unsafe blocks are great too, maybe Nim should implement them as well?
Nim does not really need unsafe blocks because unsafe features in Nim are characterized by keywords, not symbols that may be hard to search for. An unsafe block would simply say that it contains one of these keywords, which does not make any major difference for tooling.
Separately, let me address the elephant in the room: The primary reason for why we have static type systems (other than to make the compiler's job easier when optimizing and generating code) is the conjecture that static typing facilitates program correctness (because if a bug is being caught at compile time that's better than having to wait until runtime for it to show up, right?). The problem with this conjecture is that not only is it unproven, but the (admittedly sparse) research in this area seems to indicate that it may not necessarily be true, at least when it comes to developing new software (maintaining software is a different story). There seems to be pretty clear demonstrable benefit in having type annotations for interfaces (as a sort of documentation that is (reasonably) certain to be up to date), but you also seem to get that benefit in languages that check types at runtime (e.g., Dart) and appears to be an argument against type inference (at least for interfaces). And on top of that, static typing isn't a free lunch (all static type systems come with cognitive overhead and have difficulties expressing certain constructs).
As a result, there is ongoing research in the areas of soft typing, gradual typing, and software contracts that attempt to find sweet spots that maximize productivity and correctness (keeping in mind that correctness is not a binary on/off thing, but – informally – the probability of encountering a software defect). Also consider the importance of tooling: see Gilad Bracha's post on how being able to understand the state of a running program can be more important than language features.
A key insight: Static typing is at best about the absence of obviously bad things happening; it generally cannot handle subtly bad things well or guarantee that good things (i.e. correct program behavior) will result unless you go all the way to theorem proving.
This is not to say that static typing does not or cannot facilitate correctness (the evidence either way is still relatively sparse), but one should not blindly assume that it does.
@Alisaxy Not "strong typing", you mean "static typing".
@Jehan Well, what can I say. All this is fairly known to those who have experience working with both advanced dynamically typed systems, like say Smalltalk, and statically typed programming languages. Nim is fairly "nice" compared to many other statically typed languages, but its still a lot of "the same".
The amount of time and effort spent on figuring out the obscure details of what happens with the type system is HUGE compared to what the Smalltalk community spends their time on. Ok, that sentence came out a bit hard to understand. But let me rephrase it. Just scouring this forum, or the IRC log, will show that a HUGE percentage of the discussions and the "why doesn't this work" or "how do I do this" is tied into the static type system and the complexity of the language.
One can say, "well dude, programming is hard". But do realize that while we fight the compiler, the Pharo guys are building advanced moldable debuggers and live visualization solutions. :)
I really like Nim, but sometimes it scares me that people don't realize the cognitive costs here. And the blind trust of the type system to "catch bugs". Those bugs aren't the hard ones to catch. In Smalltalk I can whip out code faster and I can take the extra time to write some nice unit tests to go with it, and might even end up catching more bugs that way.
But don't get me wrong - I like Nim. But it would be even more AWESOME if the Nim community could try to make Nim do some of the stuff that the dynamic languages are brilliant at. That's where the challenge lies. Not doing the same stuff that everyone else already is doing. Try to break the mold. Step out of the box.
For example, it would be super to hear more thoughts on how a live reflective Nim development environment could be realized?
Since unions were mentioned, I think object variants are more of a burden to work with for simple sum types because of the need to create a new enum for every new combination. I think that's something macros can help with probably? Something like going from a type (Foo | Bar) to (Foo | Bar | Baz) seems like it would be troublesome to match up the two discriminators with different enum types.
Edit: I'm not saying Rust, or anything else in particular, is ideal in this situation.
@gokr: The problem is that we don't really know one way or the other and it may very well vary by application domain. The evidence that we have so far is fairly sparse and not conclusive one way or the other; but that also means that we should be careful with basing design considerations on conjectures, no matter how intuitive they sound.
@Matt_S: That's a syntax problem, you can already hack around that using macros and reduce the verbosity (you can also use inheritance instead, depending on your application domain). In the long term, I'd like to see first-class language support, though I'd personally favor something like sealed case classes in Scala.
@Jehan: Yes, I am convinced it varies by domain. And I agree that "we don't really know" if you speak in general, but I also doubt one can speak "in general" here.
If we speak of correctness ONLY then one can always make the claim that the absolutely most advanced static type system with the biggest cognitive overhead :) will be superior, but then completely ignoring how much time it takes to finish the system. In some domains correctness is vastly more important than the time and effort it takes, like say hardware coupled software where you can't afford redesigning hardware, or software directly controlling lives etc.
Further, all due respect to research in the field - but I suspect that most of these experiments (all?) are on toy tasks using students. And that kinda makes it pretty far from the real world.
But then there is a vast amount of domains, where most of us work, in which correctness competes with other goals, like time to market, time to adapt to changed requirements, amount of resources needed to make the system (number of developers) etc. And here there are numerous examples where Smalltalk for example, has outperformed static languages by a wide margin.
Problem is that most people don't know that there are huge very advanced and very important systems being built in dynamically typed languages. Take the Kapital system at JPMorgan. We are talking 60 people in the team, 14000 classes. This is the system JP Morgan dominates the derivatives market with, its INSANELY important to them and handles huge amounts of money. And they picked Smalltalk to build it, and estimates that it would have taken 3x more time to build it in Java. I would personally even suspect it couldn't realistically be done in Java, since they rely heavily on meta modelling.