On Thursday, 18 October 2018 at 18:24:47 UTC, Manu wrote:

I have demonstrated these usability considerations in production. I am
confident it's the right balance.

Then convince us. So far you haven't.

I propose:
1. Normal people don't write thread-safety, a very small number of unusual people do this. I feel very good about biasing 100% of the cognitive load INSIDE the shared method. This means the expert, and ONLY the expert, must make decisions about thread-safety implementation.

No argument.

2. Implicit conversion allows users to safely interact with safe things without doing unsafe casts. I think it's a complete design fail if you expect any user anywhere to perform an unsafe cast to call a perfectly thread-safe function. The user might not properly understand
their obligations.

Disagreed. "Normal" people wouldn't be doing any unsafe casts and *must not be able to* do something as unsafe as silent promotion of thread-local mutable data to shared. But it's perfectly fine for the "expert" user to do such a promotion, explicitly.

3. The practical result of the above is, any complexity relating to safety is completely owned by the threadsafe author, and not cascaded to the user. You can't expect users to understand, and make correct
decisions about threadsafety. Safety should be default position.

Exactly. And an implicit conversion from mutable to shared isn't safe at all.

I recognise the potential loss of an unsafe optimised thread-local path. 1. This truly isn't a big deal. If this is really hurting you, you
will notice on the profiler, and deploy a thread-exclusive path
assuming the context supports it.

2. I will trade that for confidence in safe interaction every day of
the week. Safety is the right default position here.

Does not compute. Either you're an "expert" from above and live with that burden, or you're not.

2. You just need to make the unsafe thread-exclusive variant explicit, eg:

struct ThreadSafe
{
    private int x;
    void unsafeIncrement() // <- make it explicit
    {
++x; // User has asserted that no sharing is possible, no reason to use atomics
    }
    void increment() shared
    {
       atomicIncrement(&x); // object may be shared
    }
}

No. The above code is not thread-safe at all. The private int *must* be declared shared. Then it becomes:

struct ThreadSafe {
// These must be *required* if you want to assert any thread safety. Be nice
    // if the compiler did that for us.
    @disable this(this);
    @disable void opAssign(typeof(this));

    private shared int x; // <- *must* be shared

    void unsafeIncrement() @system {
        x.assumeUnshared += 1;
    }

    // or deduced:

void unsafeIncrement()() // assumeUnshared must be @system, thus this unsafeIncrement will also be deduced @system
    {
        x.assumeUnshared += 1;
    }

    void increment() shared {
        x.atomicOp!"+="(1);
    }
}

I think this is quiet a reasonable and clearly documented compromise.

With the fixes above, it is. Without them, it will only be apparent from documentation, and who writes, or reads, that?..

I think absolutely-reliably-threadsafe-by-default is the right default position.

But it is exactly the opposite of automatic promotions from mutable to shared!

Reply via email to