“type-safe memory reuse is all that it takes for memory safety.”
The author doesn’t mention an important disadvantage, which is that you can run out of memory, even though you have lots of free space, due to all of the memory being tied up in type-specific heaps. Once a block of memory is allocated, that memory can never be reused for an object of a different type.
If your program runs in phases, where the initial phase allocates lots of temporary objects of types that aren’t used in subsequent phases, then the memory allocated during the initial phase cannot be reused in subsequent phases, even though that memory might be “free”.
The author mentions the use case of interoperability with other programming languages. Well, if I am calling a library written in this modified Nim that uses “type-safe memory reuse”, then none of the memory allocated during calls to this library can ever be freed for reuse by my code (which is written in a different language).
UPDATE: The article has a link to a paper about Cling: “Cling constrains memory allocation to allow address space reuse only among objects of the same type.” This clarifies that memory can be reused for objects of different types, but address space cannot be reused. That makes the technique more practical, at least on 64 bit architectures.
Cling also does not provide absolute memory safety, as found in Rust. Instead, it is meant to make C++ more memory safe than it currently is. Section 3.6 of the Cling paper mentions some remaining use-after-free vulnerabilities that Cling doesn’t fix.
Go’s sync.Pool is effectively a type-specific memory allocator, and it deals with the scenario where memory needs to be redistributed across types by really-freeing the free pool at GC time. (I know GC + pools is not in line with the author’s goals here, but seems related/interesting enough to talk about.)
In Go, Pool misuse in a multithreaded context can violate memory safety, because there’s no static guarantee one thread isn’t reading while another writes, and using a ‘torn’ version of some internal data structures (slice headers, interface references, etc.) breaks memory safety. It sounds like Nim avoids this with per-thread pools.
I wonder how expensive it typically is to make a pool somewhat misuse-resistant, e.g. complain if the same object is inserted twice and automatically zero it on insert so in hopes some uses after free will crash or fail. With a GC’s help, you could also yell if anything in a Pool was also referenced from non-Pool memory at GC time [oh: hard to pull off in a concurrent-GC context, though].
If that were cheap enough that you could do it for most production pools, that would be super cool, since pools are a step back towards manual memory management that can really get you out of a pickle sometimes (when relatively few alloc/dealloc sites are responsible for a lot of your allocs), but also carries some obvious danger.
The idea of type-specific memory allocators is an interesting “lateral thinking” approach.
As to the owned refs, personally I didn’t manage to grasp the core idea, unfortunately. Can someone try to explain it to me in different words? Also, is that meant to be similar to what the Pony language is doing, or not?
Each reference/pointer will either be owned or not owned. owned pointers are the ones that trigger destructors/freeing memory when they go out of scope, the rest are more like weak references. To begin with, use after free in this model can be detected in debug mode, but isn’t statically proven to not exist like in rust. The author says in the future the compiler can check the common cases statically anyway.
It seems like a weakly checked version of what rust does with borrowing.
Reminds me of fixed-sized, memory pools that I learned studying game development. That was mainly for performance. It was also a memory, safety technique. Ada had it in form of storage pools.
Note: there’s some interesting community discussion on the Nim forum.
Anyone curious about such static analysis can look at Undangle from Microsoft. Digging it up led to more stuff on mitigating dangling pointers. Stay tuned. :)
owned ref / ref
owned ref / ref
The author mentions Rust’s Ownership and Borrowing semantics briefly, but I’m curious how the proposed solution of owned ref/ref work without the additional type information Rust relies on to determine what’s valid.