On Tuesday, 4 April 2017 at 21:56:37 UTC, Atila Neves wrote:
I feel dirty if I write `__gshared`. I sneeze when I read it. But everytime I try and use `shared` I get trouble for it.

TIL that if I want a struct to be both `shared` and not, destructors are out of the way. Because while constructors are easy because we can have more than one:

struct Foo {
    this(this T)() { }
}

auto f1 = const Foo();
auto f2 = shared Foo();

There can be only one destructor:


struct Bar {
    this(this T)() { }
~this() {} // no shared, if there was the problem would reverse
    // ~this(this T)() {} isn't a thing
}

auto b1 = const Bar();
// Error: non-shared method foo.Bar.~this is not callable using a shared object
// auto b2 = shared Bar(); //oops

The reason why what I was trying to do isn't possible is obvious in hindsight, but it's still annoying. So either code duplication or mixins, huh?

Atila

The error message pretty much tells you that multiple threads should not be allowed to call the destructor concurrently, i.e. you should somehow guarantee that by the end of the scope only one thread has access to the object, which is what should happen in most multi-threaded programs.

A workable, but non the less dirty way of sharing RAII objects would be something along the lines:

struct Widget
{
    this()  { /* ... */ }

    void doWork() scope shared { /* ... */ }

    ~this() { /* ... */ }
}

Owner thread A
{
  /* 0) Make a new widget w/ automatic
     storage (RAII). Note: calling
     non-shared constructor since
     construction is a one thread endeavor
     anyway and we need non-shared `this`
     to call the destructor.             */
  auto w = Widget();

  /* 1) share `w` with other threads
     and perform some useful work...     */

  // Other thread B
  (ref scope shared(Widget) w) @safe
  {
    ssw.doWork();
  }

  /* 2) Ensure that A is now the only
     thread with reference to `w`.       */

  /* 3) w.~this() called automatically.
     Safe, since thread A is only
     one with reference to w.            */
}

2) can be achieved only if you pass `scope` references to `w` in 1), so that non of the other threads would be able to store a pointer to `w` in a variable with longer lifetime. You also need to have a way of ensuring that the other threads have shorter lifetime than `w` (i.e. simple fork-join parallelism), or you need some sort of task infrastructure that allows you to block thread A until thread B finishes working on the task created in 1) and ensuring no references have escaped in thread B.

The other approach is to not use RAII at all, but instead to use an Arc (atomic reference counting) wrapper that @trusted-ly knows to cast the 'shared' qualifier off Widget when the ref count drops to zero in order to call the destructor.

// create a non-shared Widget and transfer
// the ownership to the Arc wrapper which
// makes the object `shared` with the world.
auto arcW = Arc!(shared W)(new W());

// auto w = new W();
// auto arcFromNonUniqueW = Arc!(shared W)(w); <- doesn't compile

void use1(ref shared(W) w) @safe;
// arcW.get().use1(); // Doesn't compile:
// Error: reference to local variable arcW assigned to non-scope parameter w calling arc_test.use1

void use2(ref scope shared(W) w) @safe;
arcW.get().use2(); // OK

Reply via email to