Challenge: write a reference counted slice that works as much as possible like a built-in slice

tsbockman thomas.bockman at gmail.com
Tue Nov 9 01:14:24 UTC 2021


On Monday, 8 November 2021 at 21:42:12 UTC, Andrei Alexandrescu 
wrote:
> Eduard Staniloiu (at the time a student I was mentoring) tried 
> really hard to define a built-in slice RCSlice(T) from the 
> following spec:

Your spec is very focused on homogenizing the API for GC and RC 
slices as much as (or rather, more than) possible.

But, it isn't possible to make a truly `@safe`, general-purpose 
RC slice in D *at all* right now, even without all the additional 
constraints that you are placing on the problem.

Borrowing is required for a general-purpose RC type, so that the 
payload can actually be used without a reference to the payload 
escaping outside the lifetime of the counting reference. But, the 
effective lifetime of the counting reference is not visible to 
the `scope` borrow checker, because at any point the reference's 
destructor may be manually called, potentially `free`ing the 
payload while there is still an extant borrowed reference.

With current language semantics, the destructor (and any other 
similar operations, such as reassignment) of the reference type 
must be `@system` to prevent misuse of the destructor in `@safe` 
code.
     https://issues.dlang.org/show_bug.cgi?id=21981

The solution to this problem is to introduce some way of telling 
the compiler, "this destructor is `@safe` to call automatically 
at the end of the object's scope, but `@system` to call early or 
manually."

Also, the DIP1000 implementation is very buggy and incomplete; it 
doesn't work right for several kinds of indirections, including 
slices and `class` objects:
     
https://forum.dlang.org/thread/zsjqqeftthxtfkytrnwp@forum.dlang.org?page=1

> - work in pure code just like T[] does
> - work with qualifiers like T[] does, both RCSlice!(qual T) and 
> qual(RCSlice!T)

I wrote a semi-`@safe` reference counting system for D recently, 
which includes slices, weak references, shared references with 
atomic counting, etc. It works well enough to be useful to me 
(way better than manual `malloc` and `free`), but not well enough 
to be a candidate for the standard library due to the above 
compiler bugs.

`RCSlice!(qual T)` is no problem; the reference count and the 
payload do not need to use the same qualifiers. Whether the count 
is `shared` or not can be tracked statically as part of the RC 
type, so the "const might be immutable, and therefore have a 
shared reference count, and therefore require expensive atomic 
operations" issue that you raise is easy enough to solve.

On the other hand, `pure` compatibility and a usable 
`immutable(RCSlice!T)` are mutually exclusive, I think:

**Either** the reference count is part of the target of the RC 
type's internal indirection, in which case it can be mutated in 
`pure` code, but is frozen by an outer, transitive `immutable`, 
**or** the reference count is conceptualized as an entry in a 
separate global data structure which can be located by using the 
address of the payload as a key, meaning that incrementing it 
does not mutate the target, but is im`pure`.

I believe the former solution (compatible with `pure`, but not 
outer `immutable`) is preferable since it is the most honest, 
least weird solution, and therefore least likely to trip up 
either the compiler developers or the users somehow.

What you seem to be asking for instead is a way to trick the type 
system into agreeing that mutating a reference count doesn't 
actually mutate anything, which is nonsense. If that's really 
necessary for some reason, it needs to be special cased into the 
language spec, like how `pure` explicitly permits memory 
allocation.


More information about the Digitalmars-d mailing list