Scope as reference-counting modifier; amnesic

Michel Fortin michel.fortin at michelf.com
Thu Feb 7 05:48:38 PST 2008


On 2008-02-05 23:45:42 -0500, Edward Diener 
<eddielee_no_spam_here at tropicsoft.com> said:

>>> Michel Fortin wrote:
>>> 
>>>> But there are still many holes in this scheme in which scope now means 
>>>> reference-counted. Take this example:
>>>> 
>>>>     class A {
>>>>         void doSomething() {
>>>>             globalReferences ~= this;
>>>>         }
>>>>     }
>>>>     scope class B { }
>>>> 
>>>>     A[] globalReferences;
>>>> 
>>>>     scope B b = new B; // Scope could be made implicit here, but it's 
>>>> irrelevant to my example
>>>>     b.doSomething();
> 
> I am lost about what you are saying above. Member functions have 
> nothing to do with 'scope'.

The thing is that if we have a static reference-counting type modifier 
(the scope keyword in this case), the compiler has to emit code to 
increment and decrement the reference count each time we add or remove 
a reference to a scope object. To do that, it has to know when 
compiling a function whether or not the object's type is scope.

In the above example, class A has a doSomething function which adds a 
reference to itself to some global variable. Since 'this' is of type A 
(not scope A) in the doSomething function, no code is added to 
reference-count the object when assigning it to the global variable. 
Hence, if you could call doSomething on a scope A, and then remove all 
other references to A, A's reference count would become zero and A 
would be deleted despite it being still referenced.

(Having a B class derived from A just makes the thing harder to spot. 
It's basically the same thing as having a scope A object though.)

The obvious solution is this:

    class A {
        void doSomething() {
            globalReferences ~= this;
        }
        scope void doSomething() { // scope is an attribute of the 
function here
            globalReferences ~= this;
        }
    }

where one doSomething has no code to maintain the reference counter and 
the other version has. The first version would be called when the 
compiler has a non-scope A while the second would be called for a scope 
A.

That essentially mean that you couldn't call a scope function on a 
non-scope object and vice-versa. Each function therefore needs to be 
duplicated, with a scope and a non-scope variant, just in case it puts 
a reference to the object somewhere that'll still exist after the 
function call.

There is an obvious solution to that problem though: as the D source 
code for the two member functions is the same, the compiler could just 
compile the two variants from the same source. Unfortunately, the 
compiler would *always* have to generate two symbols for each member 
function, even if no reference is put elsewhere so the scope function 
can be reached when calling from elsewhere. (Remember that when doing a 
function call the compiler doesn't know what happens inside the 
function, and it can't just guess the scope function doesn't exist.)

    class A {
		// automatically generates the two functions from the example above
        void doSomething() {
            globalReferences ~= this;
        }
    }

If we had a new keyword to tell in the function signature that we won't 
take keep the reference somewhere else, that it'll be completely 
forgotten after the call, then we could avoid generating two functions 
needlessly for most member functions. Let's call this keyword "amnesic":

    class A {
        amnesic void doSomething() {
            globalReferences ~= this; // illegal, amnesic reference to 
this put outside function scope
        }
        amnesic void doSomethingElse() {
			// only one generated function
        }
    }

It's basically the same pattern as for invariant and non-invariant 
methods (you can't call an invariant method on a mutable object; you 
can't call a mutable method on an invariant object; both work with 
const). Here, you have regular methods, scope methods, and amnesic 
methods can work with both scope and non-scope objects.

I'm going a little off-topic now, but a new keyword such as this could 
be useful for creating invariant objects too.

Basically, while an amnesic function guaranties there are no more 
references to the object after the function call than there were 
before, an amnesic constructor could guaranty uniqueness of the 
reference after the creation. This means the created object could 
become invariant if that was the caller's intent:

    class A {
        amnesic this() {
			// legal: no reference given to the outside world
        }
        amnesic this() {
            globalReferences ~= this; // illegal, amnesic reference to 
this put outside constructor's scope
        }
    }

	A a1 = new A;
	invariant A a2 = new A;

(Others have talked about "unique" as a keyword, but "unique" isn't 
very useful because it describes the state of a reference at a certain 
point in time, not a property of a variable or a type.)

You could also have amnesic parameters to functions that would guaranty 
the function doesn't keep a reference after the call.

-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/




More information about the Digitalmars-d mailing list