Possible to pass a member function to spawn?

Artur Skawina art.08.09 at gmail.com
Wed Feb 8 15:50:06 PST 2012


On 02/08/12 22:47, Timon Gehr wrote:
> On 02/08/2012 10:26 PM, Artur Skawina wrote:
>> On 02/08/12 19:51, Timon Gehr wrote:
>>> On 02/08/2012 07:30 PM, Artur Skawina wrote:
>>>> On 02/08/12 18:24, Timon Gehr wrote:
>>>>>
>>>>> Open issue:
>>>>>
>>>>> void main(){
>>>>>       unique x = new C;
>>>>>       pragma(msg, typeof(x.z)); // ???
>>>>>       auto y = x.z; // error?
>>>>>       immutable z = x;
>>>>> }
>>>>>
>>>>> x.z is not necessarily unique, but assigning it to y breaks the guarantees of the unique qualifier of x.
>>>>
>>>> This is why treating "unique" as a static storage class doesn't really work. One
>>>> of the two last lines above would have to be disallowed,
>>>
>>>
>>> Actually that is not true. It could type check, because y is not alive after x has been cast to immutable.
>>
>> I assumed this was a simplified example and both 'y' and 'z' would be accessed later.
>>
> 
> Ok, sorry about that.
> 
>>> The problem shows here:
>>>
>>> void main(){
>>>      unique x = new C;
>>>      auto y = x.z;
>>>      immutable z = x;
>>>      foo(y,z);
>>> }
>>
>> But in real code the situation will be more complex, and like i said, while i
>> think it can be done in theory, i don't think it should. Consider your two above
>> examples - you want to allow something that's not really useful (dead assignment,
>> that will be eliminated by an optimizing compiler), while not really solving
>> the real case (for which there's no ideal solution, if it were to be legal).
>>
>>>> and that reduces its usefulness dramatically.
>>>
>>> Can you show me an example of its use?
>>
>> I think that introducing a new type modifier/class might not be necessary. What if
>> in all these examples we were to use "auto" instead of "unique" when declaring the
>> variables _and_ have the compiler internally keep a 'unique' flag? It can then do
>> the safe conversions implicitly, and this is still backwards compatible.
> 
> How do you pass ownership of an object graph between threads if there is no explicit representation of ownership in the type system?
> 
>> The problematic cases are a) accessing the type directly (ie typeof etc) and b) multiple
>> conflicting assignments.
>>
>> Also, consider your original example from this thread:
>>
>>> void main(){
>>>      auto x = new C; // pure or scope
>>>      auto y = x;
>>>      y.foo(); // pure or scope
>>>      spawn(&bar, x); // fine!
>>> }
>>
>> What if 'x' is used after the call to spawn()?
> 
> Then it does not implicitly convert to unique. This example relied on a modified definition of spawn whose sketch you did not quote.
> 
>> If we effectively passed ownership of our unique instance to another context, 'x' can no longer
>> be "unique". If it were to mutate to the target type, then leaving it
>> accessible from the current context should be reasonably safe.
> 
> The idea was that spawn could take unique class references and pass ownership to a different thread -- eliminating the need to cast to and from shared.

I'll rephrase what i said in that d.learn post; *all* I'm suggesting is this:

a) Any result of an expression that the compiler can determine is unique is
   internally flagged as such. This means eg array concatenation or new-expressions.
   Just a simple bitflag set, in addition to the the stored "real" type.
b) Any access to the data clears this flag (with just a few exceptions, below).
c) If the expression needs to be implicitly converted to another type *and*
   no implicit cast is possible *and* the "unique" flag is set - then additional
   safe conversions are tried, and if one succeeds, the "unique" flag gets cleared
   and the type gets modified to the that of the target.

This allows for things which are 100% safe, but currently prohibited by the
compiler and require explicit casts.

If I understood you right, you'd like (b) to be much less restrictive, which i
think complicates things too much. Some (b)-restrictions for cases that always
are cheap to discover /can/ be removed, but this needs to be determined on a case-
-by-case basis. Eg. I think any leaked refs to the data don't qualify (IOW any
assignment, even if only indirectly via this expression, needs to clear the flag).
One thing the (b) probably /has/ to allow is storing the result in an "auto"
variable. But making another copy should clear the flag.

While i originally needed this for immutable/const/mutable, it would also work
for shared. If spawn() takes a "shared" argument, passing it a "unique" one
will work too. And i'm not even convinced the ref needs to disappear from the
current context (obviously accessing the now shared data has to treat it as such
- but this is not different from what we had before, when using explicit casts;
in fact now it's marked as "shared" so it should be safer.)

So the question is: does having an explicit "unique" storage class improve
things further? 

Other than using it [1] to mark things as unique that the compiler can't figure
out by itself.

[1] I'm using "unique", but if it were to become a keyword it should be
    "uniq" or "@uniq", for the same reasons as "int", "auto" or "ref".

>> (Unless the new type is "unique" too - but allowing this only when there
>> are no further accesses to 'x' should be enough)
>>
>>
>>>> The "auto" assignment either has to be illegal, or
>>>> trigger a mutation, eg to immutable. I don't think having the compiler keep
>>>> track of all possible relations would be a practical solution, even if theoretically
>>>> possible.
>>>
>>> I think it would be practical enough.
>>>
>>> typeof(C.z) global;
>>>
>>> void main(){
>>>      unique x = new C;
>>>      auto y = x.z;
>>>      static assert(!is(typeof(y)==immutable));
>>>      static assert(!is(typeof({global = y;}));
>>>      immutable z = x;
>>>      static assert(is(typeof(y)==immutable));
>>>      static assert(is(typeof(x)==immutable);
>>>      foo(y,z);
>>> }
>>
>> 'y's type changed here - this wouldn't have worked for a "static" unique type.
> 
> I know. A unique field / static variable cannot be copied, only moved:
> 
> unique int[] a = [1,2,3,4];
> 
> void main(){
>     unique b = a;
>     a = null;
> }
> 
>> And if it can work for "auto" - is an explicit "unique" for 'x' needed?
>> This example *could* work, but the interesting cases are the ones when the
>> assignments/mutations are non-trivial and done because the data is actually
>> used - i'm worried that doing the analysis *then* would be too expensive.
>>
> 
> I don't think so.

Consider a container, that you need to verify and/or extract some data from,
before passing it on to somewhere else. All the accesses before this handover 
happens may indeed be safe and no reference be live anymore. But the compiler
needs to *prove* this before allowing the conversion to happen.
This could be very expensive and because it is not an optimization, but a
correctness issue, the check can *not* be disabled. Every D compiler now has
to do it, every time.

artur


More information about the Digitalmars-d mailing list