Memory models
You have indeed several memory models around:
- Manual memory handling
C and C++ provide very fine-grained control over memory allocation; heap memory can be explicitly allocated and de-allocated. - Full garbage collection
The majority of modern languages expose a memory model that uses the heap for everything, ranging from Java to Go to JavaScript to Python to Ruby to Haskell. - Garbage collection with value types and references
C# for instance is essentially garbage-collected, but features value types which are guaranteed to be stack-allocated if in local variables. - Manual memory handling with reference-counted types
This is where Delphi shines with its Copy-On-Write (COW) types and reference-countedinterface
, and also Objective C with its ARC model (which is an more sophisticated approach to reference counting). - Safe manual memory management
Rust falls into this category: you can choose where your object will be allocated (heap or stack), and how tasks work with it (it minimizes sharing by default, in contrast to other models).
But we will see that Delphi is not far away from it.
Rust memory model
Rust allocates objects in a task-oriented manner (extracted from their language definition):
Rust has a memory model centered around concurrently-executing tasks.
Thus its memory model and its concurrency model are best discussed simultaneously, as parts of each only make sense when considered from the perspective of the other.When reading about the memory model, keep in mind that it is partitioned in order to support tasks; and when reading about tasks, keep in mind that their isolation and communication mechanisms are only possible due to the ownership and lifetime semantics of the memory model.
So the memory model is the following:
A Rust program's memory consists of a static set of items, a set of tasks each with its own stack, and a heap.
Immutable portions of the heap may be shared between tasks, mutable portions may not.
The immutability of memory portions does make sense, but I tend to like the
Delphi COW approach very much.
For instance, local variables in Rust are immutable unless declared with
let mut
.
Rust features a task-oriented memory model, which purpose is to avoid full
Garbage-Collection memory model.
The basic idea is to remove garbage collection from the core language
and relegate it to the standard library, with a minimal set of language hooks
in place to allow for flexible, pluggable automatic memory management.
As a consequence, you can allocate your objects from the stack or the heap - this is a difficult concept to grasp for many programmers used to languages like Java or C# that don’t make such a distinction.
In Rust, a box is a reference to a heap allocation holding another value.
There are two kinds of boxes: managed boxes and owned boxes.
- A managed box type or value is constructed by the prefix
@
. - An owned box type or value is constructed by the prefix
~
.
In short:
is just the Rust equivalent of the C malloc and free functions;~
@
is a replacement for manual reference counting in C programs (i.e. when your value need to be managed outside the immediate execution scope);&
will define a "borrowed pointer", i.e. a reference to the object.
It results in a much better performance scalability, when used in concurrent mode.
For a more complete introduction to Rust's memory model, see this great article.
On Delphi side
Not object
instead of class
- but
object
in conjunction with class/interface
for most
high-level objects.
There are a lot of opportunities when it does make sense to have:
- Individual objects allocated locally on stack within a method/function scope;
- Set of objects allocated at once on a [dynamic] array (local or shared);
- Map some binary content with strongly-typed pointers and
object
methods.
record
, but object
allow
inheritance, which is IMHO mandatory to follow proper OOP
design - the Single
Responsibility principle, for instance.In Domain-Driven Design,
objects
are great to define value objects.In my opinion, the Delphi language, in its current state, features a large
panel of
Reducing the Delphi language to just one string type or just one memory model
(like with the
Delphi NextGen compiler) is not a good idea.
It sounds like a lack of vision to me.
Some years ago, we were told by EMB that Garbage Collection was the
future.
Now - thanks to the Apple hipe - the ARC model is the new model to
follow.
When you take a wide look, e.g. when you look at Rust, you can see that a less
monolithic approach (like the one existing on OldGen Delphi) could
make sense!
From my understanding, the object pascal memory model (with dedicated
class
and object
types) may be more easy to work with
than the Rust @ ~
obfuscated syntax.
And you are not stuck by hard coded principles like default immutability of
tasks: in Delphi, you can still use a global heap, and rely on dedicated
structures when you want better performance.
This is what we tried to do with mORMot: high-level methods are easy
to work with, but its internal core uses low-level tricks (like pointer
arithmetic or stack-allocated structures) to ensure the best possible
scalability.
In a practical point of view, Object Pascal's Manual memory
handling with reference-counted types does still make sense.
In addition to the initial COW paradigm, the RCU (Read-Copy-Update) pattern could make
sense: when used in multi-thread context, it allows lock-free shared access to
resources (which is not allowed by Rust).
The proposal of a threadlocalvar
compiler enhancement
- in
this very same blog, back in 2010 - still does make sense, and perfectly
fit with the memory model proposed by Rust.
A custom mode of class
allocation could make sense also, by
overriding the low-level allocation/release model of TObject
to
allocate on the heap by default, but on the stack (with auto-release at the end
of scope) with an optional pattern.
It is a pity that the object
type is just broken since
Delphi 2010.
Feel free to give
your feedback on our forum, as usual.