I'm a newbie in both Nim and Rust.
As far as I know, Rust's biggest selling point is memory safety without a garbage collector, and thread safety.
Now Nim 1.4 has ORC. Given the use of ORC, is Nim on par with Rust in terms of memory safety without GC now (assuming coder only relies on ORC, and does not explicitly allocate memory)? How about thread safety?
Thanks and sorry for a newbie question if it's actually obvious. :-)
They both are memory safe and both use reference counting when the static mechanisms don't suffice. How far you can get without reference counting depends on the coding style and problem domain. If you have to use the refcounting then Rust quickly becomes tedious with its Arc<Cell<T>> verbosity.
Currently Rust does the pointer "borrowing" better than Nim and also the nil/null checking but Nim is catching up. Nim is frequently regarded to be much easier to use.
The thread safety is accomplished in different ways, in Rust the refcounts become atomic operations, in Nim full subgraphs are moved between threads. However, this is still terribly under-developed, in Nim version 1.0 message passing was used instead with deepcopy of messages. It always was memory and threadsafe too. Nim does not focus on memory safety only, it can also prevents deadlocks at compile-time, can check for out-of-range integer values and with ORC can deal with graphs that form cycles.
@Araq: Nim does not focus on memory safety only, it can also prevents deadlocks at compile-time,...
Rust cannot prevent deadlocks at compile-time. I am curious what mechanism does Nim use to prevent deadlocks?
Here is how: https://nim-lang.org/docs/manual_experimental.html#guards-and-locks-lock-levels
It was never used much by anybody as far as I know because Nim version 1 was so bad at shared memory that people used message passing instead which has no locking requirements.
I don't want to rain on everyone's parade (both Nim and Rust), but regarding thread safety none of Nim or Rust or any language besides maybe Ada/Sparks and Haskell address what is needed to write concurrent data structure safely.
In both Rust or Nim the borrow checker or sink/lent annotations will ensure that you move ownership across threads instead of accidentally sharing or free-ing what you don't own. However to prove that your concurrent data structures are without races, deadlocks or livelocks you need formal verification.
Rust does have a library for that https://github.com/tokio-rs/loom because a borrow checker is not enough contrary to the usual claims about "concurrency without data races".
This is the second part of my RFC https://github.com/nim-lang/RFCs/issues/222 Correct-by-construction thread synchronization which I started to research in https://github.com/mratsim/weave/issues/18 and implement in https://github.com/mratsim/weave/pull/127 (but no time to finish)
what is needed to write concurrent data structure safely.
While that's true, it's not clear how much this affects most users of Rust or Nim (or C++ for that matter). The general idea is to have a container abstraction (queues, channels, etc). You need to ensure that all the tricky unsafe bits remain encapsulated properly by exposing a safe API. Borrow checking surely seems to help with that.
I find Nim less safe than Rust because its unsafe features aren’t segregated in any way. Both languages have unsafe features, and those are necessary to interface with C or to implement low level functionality. But Rust has the brilliant “unsafe” keyword — unsafe language features and C APIs can only be used inside a block tagged with “unsafe”, and a function whose behavior is unsafe can be tagged “unsafe” so it can only be used in an unsafe block.
What this does is make it very clear what is safe and what isn’t. That’s a big help with code and security reviews, lets you make better decisions about what 3rd party libraries to use, and can help you avoid accidentally compromising safety of existing code when modifying it.
I wonder how hard this would be to add to Nim? At a first glance, unsafety seems like an attribute that could be tracked at the function level by Nim's existing effects system. But the compiler would need to know to add this effect when any unsafe language construct is used.
Nim already does something like this with types. As it is, every time you want to bind a socket to a port, you can't pass in a number, but have to say something like Port(1234). There are similar uses with TaintedStrings and type SQL = distinct string. This model could be extended to stuff like having to pass ConnectedSocket(client), UnboundServerSocket(s) to certain functions, but that gets tedious.
Also similar, and also with blocks: guards and locks.
So I think the disallowTag RFC is interesting.
It's called unsafe because it's a property of the scope; not a function.
unsafe {} in Rust is not a contract by the developer, it's a checkbox that disable a few compiler checks that would otherwise make some necessary low level operation impossible.
It's not the developer telling other developer "hey this is safe to use", it's the developer telling the compiler "hey I want to do some unsafe thing here, don't bother me". It's cultural that the Rust community expects developer to make sure the code wrote in unsafe block is actually safe / sanitized - rightfully or not, everyone is free to have their own opinion.
While restricting the code you can write at the compiler level is tempting for improving safety, I don't think that it's has ever been really successful regarding adoption (Ada being a prime example of that).
On the other side of the spectrum Javascript and Python have great adoption to the point that their respective VM are now being ported to microcontroller (look up Micropython, Zerynth and Jerryscript for more info). I'd say adoption trumps safety for programming language.
Making it harder to write code is not a successful model for a multi-purpose language if increasing adoption is your goal.
On the other hand, using tag / effects to track unsafe calls (much like thread and sideEffect) and leaving the developer free to specifically forbid or enable said effects seems like a much better solution. But that means it's probably not gonna be some sort of unsafe block but something closer to a flag / pragma applied to an entire proc.
What's unsafe is dereferencing the pointers they produce.
While technically correct, that is a dangerous path to safety:
type
Obj = object
a: array[3, int]
b: int
var x: Obj
let theEnd = addr(x.a[3]) # safe by Rust's definition?
# unsafe by Nim's!
theEnd[] = 4 # unsafe: stores into x.b!
If you seek to detect these invalid derefs at runtime, be prepared for a harder time with Rust's notion of safety.
So every time I want to get something from a Table, I'd need to do [...] No thank you.
No, you wouldn't, because indexing a Table with [] is not unsafe. Its failure case has the well-defined and perfectly safe behavior of raising a KeyError exception.
While restricting the code you can write at the compiler level is tempting for improving safety, I don't think that it's has ever been really successful regarding adoption (Ada being a prime example of that).
Nearly every language does this, especially statically-typed ones. What are type checks, if not a compiler restriction to improve safety? Or function prototypes, or uninitialized-variable errors, or checked exceptions. BCPL is the only systems programming language I can think of without safety nets; as time went on, languages added more and more of them.
I don't see this as "restricting the code you can write at the compiler level". I see it as the compiler helping you by keeping track of potentially dangerous things.
So here's a slightly different idea: Do safety analysis similarly to Nim's (currently experimental) side-effect analysis. Instead of requiring an unsafe block at the site where the unsafe operation happens, the compiler just tags the function as unsafe, plus functions that call it.
This has no effect until you explicitly tag a function as "safe". Then the compiler will complain if the safe function calls something that's (directly or indirectly) unsafe, flagging that call. At that point you look for an appropriate place to wrap an actuallySafe block, signifying that there are sufficient checks. Or otherwise you could just decide to take the safe attribute off the function.
After just writing a bunch of 'cast: gcsafe' the idea of a 'safe' annotation would get annoying very quickly if it bubbled up. Nim libraries reuse of C libraries quite often. If Nim were to adopt something of Rust's style I'd prefer a block annotation that'd enable cast/addr but not propagate an effect. The only reason would be to enforce a easy-to-grep-for marker that some code needs extra attention.
Overall I find Nim's style much more approachable than Rust's. With a tiny bit of discipline and a few unit tests it's easy enough to wrap C libraries into safe Nim API's that'll be pretty much equal to Rust's safety in wrapping C code.
Also, I've come to appreciate the idea of gradual improvement. It seems it'd be possible to write static analyzers for Nim code that could let you check whatever set of properties you wanted to, possibly by custom tags. Then a team can start out with quick-and-dirty code and gradually tighten restrictions as needed/desired. Making that ability first class would provide more payoff for the effort required than safe/unsafe blocks.
Though I will admit I've had a few more null pointer exceptions than I'd prefer in plain Nim code. If there's any big "unsafety" issue in Nim compared to Rust it's how easy it is to forget to initialize a 'ref variable'. It doesn't lead to buffer overflows so I don't think it's a security issue. More that it's annoying how often I refactor a piece of code and forget to initialize a ref variable or sequence.
> Nearly every language does this, especially statically-typed ones. What are type checks, if not a compiler restriction to improve safety?
I didn't meant safety checks are not something good, I meant that enforcing safety by-default everywhere (when it's not needed most of the time) wasn't practical. If you want to do a comparaison with type checks, a better one would be specifying type everywhere vs type inference. But we digress :).
> Do safety analysis similarly to Nim's (currently experimental) side-effect analysis. Instead of requiring an unsafe block at the site where the unsafe operation happens, the compiler just tags the function as unsafe, plus functions that call it.
This has no effect until you explicitly tag a function as "safe". Then the compiler will complain if the safe function calls something that's (directly or indirectly) unsafe, flagging that call. At that point you look for an appropriate place to wrap an actuallySafe block, signifying that there are sufficient checks. Or otherwise you could just decide to take the safe attribute off the function.
100% agree on this (it was more or less what I had in mind in my previous post). Having the compiler track the most common "unsafe" operation, leaving you the possibility to filter them would be nice.
Though I will admit I've had a few more null pointer exceptions than I'd prefer in plain Nim code. If there's any big "unsafety" issue in Nim compared to Rust it's how easy it is to forget to initialize a 'ref variable'.
I see I'm not the only one who got bitten by wild uninitialized reference :D.
I don't think that it's has ever been really successful regarding adoption (Ada being a prime example of that).
Ada is very popular in safety-critical environments, especially aeronautics. FOSDEM has a dedicated developer room with a growing community.
Personally, I wish Nim could have similar concurrency guarantees.
I think Ada guarantees for embedded in particular with Spark are about ensuring that no inputs or transitions in your state machines will produced undefined or unwanted behavior (no way to switch off autopilot for example, or locked door with no manual override).
This is something I also would like to see and I proposed a path forward using Z3: https://github.com/nim-lang/RFCs/issues/222