After working a bit more on it (accompanied by a bad flu with 40 °C fever, so 
hopefully it's not all
wrong in reality), I got a library approach that allows to use shared objects 
in a (statically
checked) safe and comfortable way. As a bonus, it also introduces an 
isolated/unique type that can
be safely moved between threads and converts safely to immutable (and mutable).

It would be really nice to get a discussion going to see if this or something 
similar should be
included in Phobos and which (if any) language extensions, that could help (or 
replace) such an
approach, are realistic to get implemented in the short term (e.g. Walter 
suggested
__unique(expression) to statically verify that an expression yields a value 
with no mutable aliasing
to the outside).

But first a rough description of the proposed system - there are three basic 
ingredients:

 - ScopedRef!T:

   wraps a type allowing only operations that are guaranteed to not leak any 
references in or out.
   This type is non-copyable but allows reference-like access to a value. In 
contrast to 'scope' it
   works recursively and also works on return values in addition to function 
parameters.

 - Isolated!T:

   Statically ensures that any contained aliasing is either immutable or is 
only reachable through
   the Isolated!T itself (*strong isolation*). This allows safe passing between 
threads and safe
   conversion to immutable. A less strict mode also allows shared aliasing 
(*weak isolation*).
   Implicit conversion to immutable is not possible for weakly isolated values, 
but they can still
   safely be moved between threads and accessed without locking or similar 
means. As such they
   provide a natural bridge between the shared and the thread local world. 
Isolated!T is
   non-copyable, but can be move()d between variables.

 - ScopedLock!T:

   Provides scoped access to shared objects. It will lock the object's mutex 
and provide access to
   its non-shared methods and fields. A convenience function lock() is used to 
construct a
   ScopedLock!T, which is also non-copyable. The type T must be weakly 
isolated, because otherwise
   it cannot be guaranteed that there are no shared references that are not 
also marked with
   'shared'.

The operations done on either of these three wrappers are forced to be (weakly) 
pure and may not
have parameters or return types that could leak references (neither /to/ nor 
/from/ the outside).

It solves a number of common usage patterns, not only removing the need for 
casts, but also
statically verifying the correctness of the code. The following example shows 
it in action. Apart
from the pure annotations ('pure:' would help), nothing else is necessary.

---
import stdx.typecons;

class Item {
        private double m_value;
        this(double value) pure { m_value = value; }
        @property double value() const pure { return m_value; }
}

class Manager {
        private {
                string m_name;
                Isolated!(Item) m_ownedItem;
                Isolated!(shared(Item)[]) m_items;
        }

        this(string name) pure
        {
                m_name = name;
                auto itm = makeIsolated!Item(3.5);
                // _move_ itm to m_ownedItem
                m_ownedItem = itm;
                // itm is now empty
        }

        void addItem(shared(Item) item) pure { m_items ~= item; }

        double getTotalValue()
        const pure {
                double sum = 0;

                // lock() is required to access shared objects
                foreach( ref itm; m_items ) sum += itm.lock().value;

                // owned objects can be accessed without locking
                sum += m_ownedItem.value;

                return sum;
        }
}

void main()
{
        import std.stdio;

        auto man = new shared(Manager)("My manager");
        { // doing multiple method calls during a single lock is no problem
                auto l = man.lock();
                l.addItem(new shared(Item)(1.5));
                l.addItem(new shared(Item)(0.5));
        }

        writefln("Total value: %s", man.lock().getTotalValue());
}
---

This all works quite well and is able to come close to what the C# system that 
I linked some days
ago (*) is able to do. Notably, ScopedRef!T allows to directly modify isolated 
objects without
having to implement the recovery rules that the paper mentions. It cannot 
capture all those cases,
but is good enough in most cases. Note that there are a lot of small details 
that I left out, but
just to hopefully better get the general idea across.

There are still some open points where I think small language changes are 
needed to make this
bullet-proof:

 - It would be nice to be able to disallow 'auto var = 
somethingThatReturnsScopedRef();'. Copying
   can nicely be disabled using '@disable this(this)', but initializing a 
variable can't. This
   opens up a possible whole:

   ---
   Isolated!MyType myvalue = ...;
   ScopedRef!int fieldref = myvalue.someIntField;
   send(someThread, myvalue); // isolated values can be safely moved to 
different threads
   fieldref++; // but wait, we can still screw it up!
   ---

 - opApply() seemingly cannot be used in a pure context in a meaningful way. 
Making it pure means
   that also the delegate that it takes must be pure. But a pure foreach body 
basically means that
   the whole loop has no effect (okay, it could still modify the iterated 
elements). The workaround
   I did was to let the pure opApply take an impure delegate that is casted to 
pure upon calling it.

 - Locking is technically an impure operation, but from a high level view it 
has no visible effect.
   To make the whole system really usable, it is required that lock() can be 
used from a pure
   context. As a workaround I declared _d_monitorenter/exit as pure (these are 
used for locking the
   object's mutex).


github project containing the D implementation:

https://github.com/s-ludwig/d-isolated-test

A little documentation:

http://vibed.org/temp/d-isolated-test/stdx/typecons/lock.html
http://vibed.org/temp/d-isolated-test/stdx/typecons/makeIsolated.html
http://vibed.org/temp/d-isolated-test/stdx/typecons/makeIsolatedArray.html


(*) Microsoft paper about the C# type system extension, from which some of the 
ideas originate:

http://research.microsoft.com/pubs/170528/msr-tr-2012-79.pdf

Reply via email to