[nim-dev] Re: Understanding memory safety compared to Golang

  • From: Jehan <behrends@xxxxxxxxx>
  • To: nim-dev@xxxxxxxxxxxxx
  • Date: Thu, 21 Jan 2016 21:16:03 +0000

No, Nim doesn't have stronger or weaker safety guarantees than Rust [1]. Rust's
memory safety is nothing new, either. It is mostly that some older languages
like C/C++ are the exceptions in not being memory-safe. There is nothing new or
magical about memory safety. LISP was already memory-safe when it was invented
in 1958. The only question is how much performance you need to trade away for
it (the value is never zero for non-trivial programs, but can vary greatly,
depending on whether the language was designed with it in mind or not).

The main difference between Rust and other languages is that it does some more
(but not all [2]) safety checks at compile time rather than at runtime. It also
allows you to avoid GC, but does not provide you any memory safety over GC.
Rust's borrow checker allows you to statically prove that references are live
[3]; a GC simply avoids deallocating any memory that has a live reference to it
(on the other hand, a GC can ensure that references remain live even where this
is hard or impossible to prove statically). The end result is the same with
respect to memory safety (the reason some people want to avoid GC is for
performance reasons, not memory safety).

Note that the runtime checks do have a potential overhead (the compiler will be
able to remove some, but not all). But this is unavoidable for memory-safe code
(being able to do so statically would imply solving the halting problem or a
language that is too restrictive for practical use). Unavoidable runtime checks
typically can occur when accessing elements in an array (especially a
dynamically sized array) or fields of a polymorphic type.

There is no ``unsafe`` keyword in Nim, but all unsafe features in Nim (such as
``ptr`` or ``addr``) have their own keywords already.


As for Nim vs. Go: Nim is more expressive, but Go is simpler. Either goal can
be preferable, depending on your requirements.

[1] There is a rather technical concern in that generating C/C++ code can
create some potential issues with undefined behavior; this is about the
backend, not the language. These can also be avoided if necessary [4, 5].

[2] You will not be able, for example, dispense with runtime boundary checks
for arrays entirely; compilers can prove that they are not needed only in some
cases, not all, since an index can be an arbitrary computable expression.

[3] Which, incidentally, is a damn impressive accomplishment.

[4] To make Nim memory-safe, compile with ``-d:safe`` or ``-d:release -d:safe``
and the following lines in your config:

```
@if safe:
gcc.options.always = "-w -fpermissive -fno-strict-overflow
-fsanitize=null,shift -fsanitize-undefined-trap-on-error"
gcc.cpp.options.always = "-w -fpermissive -fno-strict-overflow
-fsanitize=null,shift -fsanitize-undefined-trap-on-error"
clang.options.always = "-w -fpermissive -fno-strict-overflow
-fsanitize=null,shift -fsanitize-undefined-trap-on-error"
clang.cpp.options.always = "-w -fpermissive -fno-strict-overflow
-fsanitize=null,shift -fsanitize-undefined-trap-on-error"
obj_checks:on
field_checks:on
range_checks:on
bound_checks:on
@end
```

[5] AFAIK, there is also at least one remaining bug in the allocator where you
could technically violate memory safety if the object size calculations
overflow under certain circumstances. However, that is a bug, not intended
behavior.

Other related posts: