Immutable and unique in C#
Sönke Ludwig
sludwig at outerproduct.org
Tue Nov 13 01:38:36 PST 2012
Am 13.11.2012 09:40, schrieb Walter Bright:
> On 11/12/2012 11:48 PM, Sönke Ludwig wrote:
>> Am 13.11.2012 00:46, schrieb Walter Bright:
>>> On 11/9/2012 5:53 AM, Sönke Ludwig wrote:
>>>> Independent of this article I think D is currently missing out a lot by
>>>> omitting a proper unique type (a _proper_ library solution would be a
>>>> start, but I'm not sure if that can handle all details). It would make a
>>>> lot of the cases work that are currently simply not practical because of
>>>> loads of casts that are necessary.
>>>
>>> Unique as a type qualifier comes with all kinds of subtle problems. For one thing, unique is more of
>>> a property of an expression rather than a property of the type.
>>>
>>> But I was thinking - what if it's a storage class? Like ref is? Working through the use cases in my
>>> head, I think it can work. Unique being a property of an expression, this can be tested upon
>>> assignment to the unique variable. Upon reading that unique variable, the value of it is erased.
>>>
>>> Function parameters can be qualified with "unique" as well, meaning their corresponding arguments
>>> can only be unique expressions.
>>>
>>
>> Currently I also see no reason why this should not be possible. Since it is acting recursively and
>> locally anyway, a storage class will probably capture almost everything that a full type would. I
>> had thought about the same route to reduce intrusiveness, before looking first what's possible with
>> a pure library solution.
>>
>> The library solution seems surprisingly practical for some use cases. With the concept of "string"
>> and "weak" isolation (*), it can even handle the bridge between isolated and shared memory (thanks
>> to the completely separated nature of shared). By this, it solves a lot of the problems that only
>> Bartosz system was able to solve, for example declaring owned collections (no Mutex necessary),
>> which contain references to shared data, and still everything is safely checked.
>>
>> A proper language feature can still do a lot more and IMO D should get there at some point - but I
>> think this is a viable short/mid-term alternative. I would like to get some discussion going to
>> eventually include something in phobos.
>>
>>
>> (*)
>>
>> strong isolation: allows only strongly isolated and immutable references
>> -> can be passed to other threads and casts implicitly to immutable
>>
>> weak isolation: allows weakly isolated, immutable and shared references
>> -> can only be passed to other threads
>>
>
> I've thought some more about it, and there are some implementation problems with doing it as a
> storage class. Specifically, the issue of enforcing destructive reads from unique variables, which
> is a very intrusive and complex change for little gain.
>
> However, this is not an issue if, instead, we created:
>
> Unique!T v = ...;
>
> where T is a pointer/class type. It's a wrapper around T with some interesting properties. Unique!T
> can be implemented to only allow one, destructive, read. All that read does is return a value of
> type T.
>
> A Unique!T can only be initialized by:
>
> 1. a destructive read of another instance of Unique!T
> 2. an expression that can be determined to generated an isolated pointer
>
> #2 is the interesting bit. That requires some compiler support, sort of like:
>
> __unique(Expression)
>
> which will allow it through if Expression is some combination of new, pure functions, and other
> Unique pointers.
>
> There is also the issue of "I know it's a unique pointer, but the compiler cannot guarantee that, so
> how do I set it?" for which I propose that in @system functions, __unique(Expression) always says
> it's ok.
Wouldn't it be better to handle this as a special means of construction in the Unique struct, along
the lines of assumeUnique()? Assuming that still most functions are still @system, this would
severely limit the verification value of the Unique type otherwise.
>
> Note that dereferencing a Unique!T variable will always entail a destructive read of it. Therefore,
> multiple reads will require storing it into an instance of T. Calling a method with a Unique!T this
> will also require converting it into a T. Getting it back into a Unique!T will require some @system
> programming.
>
>
> P.S. Yes, I know there's std.typecons.Unique, but it has a lot of holes in it, such as:
>
> &up.a.b; // uh-oh, now we have another address
> // into the supposedly isolated graph!
Did you see my effort on this?
(http://forum.dlang.org/thread/k7orpj$1tt5$1@digitalmars.com?page=4#post-k7rhoj:24m8h:241:40digitalmars.com)
This version, instead of requiring the initialization expression to be unique, enforces that the
type cannot contain non-unique references. That is, except for shared data. Allowing shared data
greatly improves its flexibility for passing data between threads.
But because it guarantees that the type cannot contain non-unique data, it can allow multiple
dereferences and still guarantee that no references are shared. Only immutable (or shared)
references can be extracted. Everything else can only be copied or moved, keeping everything isolated.
The result is, I think, more usable because a) shared data is allowed and b) step-by-step
construction of unique data is possible naturally without constantly moving a unique pointer around.
What makes the C# system usable are mostly the automatic uniqueness-recovery rules, and I think
those can only be implemented by the compiler (setting apart AST macros ;) ).
More information about the Digitalmars-d
mailing list