> ...B/D does work well with multi-threading.
The (old) "Ownership You Can Count On" paper specifically says that the
elimination of reference counting with multi-threading is difficult and there
doesn't seem to be good proposed work around(s) that don't add greatly to the
complexity and only work when the threads are limited to not sharing memory
between them; is there newer work that overcomes that?
For the general case of memory shared between threads, B/D reverts to unowned
RC just as my proposed implementation does but my method would seem to be
simpler to implement.
> ...reference counting is an incomplete form of memory management...
>
> ...but if you start with reference counting you start from a worse position.
How is reference counting (RC) incomplete other than possibly allowing data
cycles, for which there may be solutions in forbidding the most common cases?
> ...you can't create cycles with B/D easily and with reference counting you
> can.
One can always create data reference cycles if you aren't careful in any system
as in the "halting problem" but (other than that one), most data cycle
conditions can be detected and forbidden by limiting its use. The JavaScript
transpiler "Elm" seems to be considering using a reference counting
implementation so as to be able to support WebAssembly with no built-in GC, and
therefore he has the compiler forbid any self-referencing data structure other
than as a global and then only when there is a function call to break the
cycle. So, in Nim syntax, the following would be illegal for all cases:
type Cyclic
head: owned ref int
next: Cyclic
Run
but the following is allowed at the global level:
type Cyclic
head: owned ref int
next: proc(): Cyclic
proc nextCycle(x: int): Cyclic =
Cyclic(head: makeOwnedRef(x), next: proc(): Cyclic = nextCycle(x + 1))
let cycle = nextCycle(1)
Run
where the "newCycle" can only be declared at the global level as here.
His reasoning is that at the global level nothing can be destroyed until the
end of the program where everything is automatically destroyed but that there
could be a memory leak if this were declared as a binding inside a proc
especially if memoization were applied to the thunk (as would be done for a
lazy list). In fact, I think this reasoning is flawed and this is overly
restrictive as the "memory leak" is intended when one holds onto the head of a
lazy list and the leak disappears as one consumes the lazy list and the
elements are destroyed.
F# (although it is GC'ed through DotNet) allows the same anywhere when the
cycle is broken by a function, but allows some data cycles where some of the
intermediate values are just simple bindings with a warning that there will be
runtime checking.
Specifically to the new Nim runtime, all bindings are destroyed at the end of a
block/proc1 unless they have been forwarded to another `proc as a sink/owned in
which "escaped" case the forwarded proc will destroy it when it ends unless...
and so on. This also applies to closures, whose instances must destroy their
own environment and its contained bindings when they go out of scope. Of
course, instantiation of a data race would mean that the program either hangs
in an infinite loop or aborts with a stack overflow.
To the point, isn't it simpler to avoid most cyclic data by only allowing it
when broken by a function call as a form of deferred execution at any level (no
system can totally eliminate all cyclic data, but this takes care of most run
away cases) than to implement B/D and then have to work through its own
limitations? By forbidding data self references (other than weak ones) not
behind a deferring function call, there may be limitations on the use of the
structures, but these limitations don't seem to be very restricting for
languages that do place this restriction.
I don't say that we couldn't get B/D to work for us but it will be a longger
on-going development cycle as it hasn't been commonly implemented other than as
a test project, whereas RC is known (including its limitations) and has been
often successfully used in spite of whatever its limitations might be: ie: C++,
Swift, Rust when the lifetime must escape the scope, etc.
Other than as to this perceived limitation of RC in data cycles, how does B/D
support the extended development required to make it work as compared to RC?