August 07, 2008
This post is related to the "Sharing in D" thread.  It relates first to the idea that shared variables will be very limited in what you can do with them, and second to the thought that there should be some standard "middle ground" between shared and unshared data, like "const" is to "invariant" and mutable.  I think that the middle ground is "scope", along with language-supported lockes.

I came up with the idea of Scope Locks a while back based on a comment that Walter made that "there is no way to enforce that people use locks properly."  The design I'm posting here isn't totally refined yet, but I thought that we ought to get it into the mix before the design of "shared" gets too far.

SCOPE LOCKS IN GENERAL

The concept of a Scope Lock is a lock which (transitively) protects a piece of data.  The data is actually contained *within* the lock, and cannot be accessed (even read) externally.

When you lock a scope lock, it calls a callback that you provide, passing a pointer to the data as a "scope" argument.  (Are scope arguments implemented yet?  I haven't tested it...)  Since this is a scope argument, you cannot save a copy of this pointer (or anything that it points to) after the function returns.

If you lock a Scope Lock in exclusive mode, then the scope object that you are passed is mutable, but if you lock it in shared mode, then the copy will be const.

The lock is automatically released when you return from the function.

APPLICABILITY TO SHARED

I propose that we add a new modified, "locked", along with the "shared" modifier.  "locked" variables are "shared" variables which you can make unshared by locking them.  This could happen using something like the "with" syntax:

BEGIN CODE
	locked MyStruct foo;
	void bar()
	{
		// it is illegal to use any fields of foo out here
		unlock-read(foo)
		{
			// in this block, foo is a non-shared const
			writefln("current state = ", foo.field1);
		}
		unlock-write(foo)
		{
			// in this block, foo is non-shared and mutable
			foo.field1++;
		}
	}
END CODE

We could then allow "unlock" to be applied to function arguments, as a way to unify "shared" and unshared code.  This would be syntax sugar for grabbing the lock, and then calling the function:

BEGIN CODE
	// note that this argument has to be 'scope' or else the locking
	// scheme doesn't work
	void baz(scope MyStruct *thing) {...}
	void fred()
	{
		baz(unlock-write(foo));
	}
END CODE



OPEN ISSUES & LIMITATIONS
- Scope locks don't solve deadlock issues
- Scope locks should probably allow recursive locks (no self-deadlock)
- "const locked" should probably collapse to "locked" (that is, "const" is not transitive through "locked") so that, in the case of nested locks, you can lock a member in exclusive mode even if the container is shared)
- With nested locks, there's no way to relase the outer lock while you still hold the inner (without some more language features)
- "scope" is difficult to use in some situations.  If you take two pointers to members of the same scope object, and you pass those pointers to another function, how do you express to the other that it's safe to store pointers to one in the other?



Thoughts?