Escaping the Tyranny of the GC: std.rcstring, first blood

via Digitalmars-d digitalmars-d at puremagic.com
Sun Sep 28 03:34:36 PDT 2014


On Saturday, 27 September 2014 at 11:54:45 UTC, Dmitry Olshansky 
wrote:
> 27-Sep-2014 14:23, "Marc Schütz" <schuetzm at gmx.net>" пишет:
>> AFAICS we don't gain anything from this, because it just 
>> automates
>> certain things that can already be done manually in a suitably
>> implemented wrapper struct. I don't think automation is 
>> necessary here,
>> because realistically, how many RC wrappers will there be? 
>> Ideally just
>> one, in Phobos.
>
> You must be missing something big. Ref-counting ain't singular 
> thing, it's a strategy with a multitude of implementations, see 
> my other post.

Ok, you're right about the different possible implementations. 
Still, your proposal mostly seems to propose separate functions 
opInc()/opDec() for operations that can just be placed in the 
wrappers' constructor, destructor and postblit, with no 
repetition.
(opInitRefcount() is missing in you proposal, strictly speaking; 
an automatic call to opInc() isn't enough, because initialization 
could be arbitrarily complex.)

The one thing your proposal enables is wrapper-less reference 
counting. Is that important?

>> I believe we can achieve the same efficiency without ARC with 
>> the help
>> of borrowing and multiple alias this.
>
> Problem is - there is no borrowing yet in the compiler, or 
> maybe you mean something more simple.

No, I'm talking about my proposal.

>
>> Consider the cases where inc/dec
>> can be elided:
>>
>>    RC!int a;
>>    // ...
>>    foo(a);
>>    // ...
>>    bar(a);
>>    // ...
>>
>> Under the assumption that foo() and bar() don't want to keep a 
>> copy of
>> their arguments, this is a classical use case for borrowing. 
>> No inc/dec
>> is necessary, and none will happen, if RC!int has an 
>> alias-this-ed
>> method returning a scoped reference to its payload.
>
> Interesting. However scope must work first, also passing an 
> RC!int by borrowing is this:
>
> void func(scope(A) a)
>
> or what? how does it transform scope? (Sorry I haven't followed 
> your proposals for scope)

That's how it will look from the user's point of view. The RC 
wrapper's implementer will need to add an alias this that returns 
the payload with scope:

     struct RC(T) {
         private T payload_;
         scope!this(T) borrow() { return payload_; }
         alias borrow this;
     }

It is then implicitly convertible to scope(T);

>
>
>> On the other hand, foo() and bar() could want to make copies 
>> of the
>> refcounted variable. In this case, we still wouldn't need an 
>> inc/dec,
>> but we need a way to express that. The solution is another 
>> alias-this-ed
>> method that returns a (scoped) BorrowedRC!int, which does not 
>> inc/dec on
>> construction/destruction, but does so on copying. (It's 
>> probably
>> possible to reuse RC!int for this, a separate type is likely 
>> not
>> necessary.)
>
> Who would make sure original RC still exists?

How could it not exist? If it existed immediately before the 
function call, it will also exist during the call. It can not 
have gone out of scope before the function returned.

This is already true without borrowing. Borrowing additionally 
ensures that it even works in special cases, e.g. when the called 
function makes a copy of the wrapper, and that there will be no 
references left when the function returns (that is, no references 
to the wrapper, not the payload).

>
>> The other opportunity is on moving:
>>
>>     void foo() {
>>         RC!int a;
>>         // ....
>>         bar(a);    // last statement in foo()
>>     }
>
> We should already have it with structs by their nature.

Exactly. What I want to say is that here, again, the compiler 
doesn't need to know that `a` does reference counting in order to 
generate efficient code. No RC specific optimizations are 
necessary, thus teaching the compiler about RC doesn't have 
advantages in this respect.

>
>>
>> Here, clearly `a` isn't used after the tail call. Instead of 
>> copy &
>> destroy, the compiler can resort to a move (bare bitcopy). In 
>> contrast
>> to C++, this is allowed in D.
>>
>> This covers most opportunities for elision of the ref 
>> counting. It only
>> leaves a few corner cases (e.g. `a` no longer used after 
>> non-tail calls,
>> accumulated inc/dec as in your example). I don't think these 
>> are worth
>> complicating the compiler with ARC.
>
> I don't mind having working scope and borrowing but my proposal 
> doesn't require them.

Well, implement RC in the compiler, and you still have no 
borrowing. But implement borrowing, and you get efficient RC for 
free, in addition to all the other nice things it allows.


More information about the Digitalmars-d mailing list