How to cast `shared` away reliably

Arafel er.krali at gmail.com
Thu Feb 12 17:31:03 UTC 2026


On 2/12/26 15:59, Kagamin wrote:
> Example:
> ```d
> shared class Unshareable(T)
> {
>      T value;
>      T castToUnshared()
>      {
>          synchronized(this)
>          {
>              return cast(T)value;
>          }
>      }
>      void castFromUnshared(T value2)
>      {
>          synchronized(this)
>          {
>              value=cast(shared)value2;
>          }
>      }
> }
> 
> shared Unshareable!(int[][]) a;
> 
> unittest
> {
>      int[][] b=a.castToUnshared();
>      a.castFromUnshared(b);
>      shared int[][] b2=a.value; //shared
> }
> ```

I understand that this is just a proof of concept, but this has quite a 
few of issues.

For starters, if you use value types, you are returning a copy each time 
you switch from shared to unshared and vice versa:

```d
shared class Unshareable(T)
{
     T value;
     T castToUnshared()
     {
         synchronized(this)
         {
             return cast(T)value;
         }
     }
     void castFromUnshared(T value2)
     {
         synchronized(this)
         {
             value=cast(shared)value2;
         }
     }
}

struct S {
     @disable this(this);
}

shared Unshareable!(S) a; // FAILS both in `castToUnshared` and 
`castFromUnshared`
```

And then you'll find out that `cast (T) value` happens to be an rvalue, 
so you can't just return `ref`, unlike `cast () value`... that is an 
lvalue. Welcome to the rabbit hole.

Of course you can start adding corner cases, but you end up with a mess 
making you wonder whether you should just forget about it and just go 
with `__gshared` all the way.

Here is my current approach for comparison, and the kind of solution I 
think should be, at least, in phobos:

```d
template Unshared(S) {
     static if (is(S == shared(K[V]), K,V)) {
         alias Unshared = Unshared!(K)[V];
     } else static if (is(S == shared(T[]), T)) {
         alias Unshared = Unshared!(T)[];
     } else static if (is(S == shared(T[n]), T,size_t n)) {
         alias Unshared = Unshared!(T)[n];
     } else static if (is (S == shared(T*), T)) {
         alias Unshared = Unshared!(T)*;
     } else static if (is(S == shared(T), T)) {
         alias Unshared = T;
     } else {
         alias Unshared = S;
     }
}

unittest {
     template sanity(T) {
         alias S = shared(T);
         static if(is(T == Unshared!S)) {
             enum sanity = true;
         } else {
             enum sanity = false;
         }
     }

     static assert(sanity!int);
     static assert(sanity!(int[]));
     static assert(sanity!(int[][]));
     static assert(sanity!(int[int][5][]*));
     static assert(sanity!(int[5]));
     static assert(sanity!(int[int]));
     static assert(sanity!(int*));
}

pragma(inline, true)
ref unshare(T)(return ref T t) => *(cast (Unshared!(T*)) &t);

import std;

struct S {
     @disable this(this);
     int*[] i;
     int *j;
     RefCounted!int k;
}

unittest {
     // Our original data
     S s;
     s.j = new int;
     s.k = 1;

     // Make it ready to be shared
     ref ss = cast (shared) s;

     // Send ss to other thread(s), then eventually afer being sure 
we're in critical section, get our data back
     ref us = ss.unshare;

     // Now we can do business as usual, forgetting that `shared` was 
ever involved
     int j = 42;
     us.i ~= &j;
     *(us.j) = 13;
     us.k = 2;

     // No need to switch back to shared, we changed the original value 
directly
     assert(*(s.i[0]) == 42);
     assert(*(s.j) == 13);
     assert(s.k == 2);
}
```

I find it much cleaner and more intuitive, tbh.


More information about the Digitalmars-d mailing list