October 01, 2018
On Monday, 1 October 2018 at 08:04:38 UTC, Kagamin wrote:
> Shared data may need different algorithms.

Yes, but those same algorithms will work on unshared (they might be slower but they will work). The reverse is not true, it can lead to race conditions.

>If unshared data is
> implicitly convertible to shared, you start to conflate shared data with unshared, so you're back to C-style sharing.

No, when participating in overloading, an unshared method will be preferred over a shared method for an unshared object. Same with parameters that are not `this`.

> This is how you can do it:
>
> shared struct SharedBob
> {
>     this(int){}
>     void setThing(){}
> }
> alias shared SharedBob Bob;
>
> void f(ref shared Bob a, ref Bob b)
> {
>   a.setThing(); // I have a shared object, can call shared method
>
>   b.setThing(); // ok
> }
>
> int main()
> {
>     auto b=Bob(0);
>     Bob c;
>     f(b,c);
>     return 0;
> }

I'm not sure what that was supposed to demonstrate.
October 01, 2018
On Monday, 1 October 2018 at 09:55:41 UTC, ag0aep6g wrote:
> On 10/01/2018 08:47 AM, Nicholas Wilson wrote:
>> In order to be safe, a mutable parameter can be implicitly cast to shared iff the parameter is also scope (that includes the `this` reference`). With an implicit cast in place of the explicit cast under the new rules it would fail to compile because the `this` reference is not scope.
>
> I don't see why it would fail to compile. There's no reason why my `doThing` couldn't be marked as `scope`. It doesn't leak anything.

Hmm, you are right. Its annoying because the use case for this is where the data is already shared and a lock has been taken.

> `pure` would break the example. I'm not sure if it would ensure safety, though. Can a `pure` method spawn a new thread (that outlives the method call)?

Error: pure function onlineapp.f cannot call impure function core.thread.Thread.start
October 01, 2018
On Monday, October 1, 2018 3:55:41 AM MDT ag0aep6g via Digitalmars-d wrote:
> On 10/01/2018 08:47 AM, Nicholas Wilson wrote:
> > In order to be safe, a mutable parameter can be implicitly cast to shared iff the parameter is also scope (that includes the `this` reference`). With an implicit cast in place of the explicit cast under the new rules it would fail to compile because the `this` reference is not scope.
>
> I don't see why it would fail to compile. There's no reason why my `doThing` couldn't be marked as `scope`. It doesn't leak anything.
>
> `pure` would break the example. I'm not sure if it would ensure safety, though. Can a `pure` method spawn a new thread (that outlives the method call)?

pure is not sufficient regardless of what happens with threads, because it also has to be proven that no reference can escape through any of the parameters or the return value. E.G. depending on the parameters, it could actually be possible to assign to a module-level variable inside a pure function via a pointer that was a member variable of one of the parameters. Depending on how much information the compiler has about the types involved, _maybe_ it could prove it without scope if pure is involved, but it gets pretty thorny. Certainly, it's way, way simply just to use scope and force the programmer to continue to cast in those cases that the compiler can't prove correctness just like we have to do now.

- Jonathan M Davis



October 01, 2018
On Monday, 1 October 2018 at 02:29:40 UTC, Manu wrote:
> struct Bob
> {
>   void setThing() shared;
> }
>
> As I understand, `shared` attribution intends to guarantee that I dun
> synchronisation internally.
> This method is declared shared, so if I have shared instances, I can
> call it... because it must handle thread-safety internally.
>
> void f(ref shared Bob a, ref Bob b)
> {
>   a.setThing(); // I have a shared object, can call shared method
>
>   b.setThing(); // ERROR
> }
>
Instead of making mutable->shared conversion implicit, you use template this parameters:

struct B
{
     void setThing(this T)()
     {
         static if(is(T == shared(B)))
         {
             /* do synchronisation */
         }
         else
         {
            /* all other cases */
         }
     }
}


I think that it's great that mutable(T) is not implicitly convertible to shared(T) as it makes synchronization bugs a lot more obvious.

Cheers,
RazvanN
October 01, 2018
On 01.10.2018 04:29, Manu wrote:
> struct Bob
> {
>    void setThing() shared;
> }
> 
> As I understand, `shared` attribution intends to guarantee that I dun
> synchronisation internally.
> This method is declared shared, so if I have shared instances, I can
> call it... because it must handle thread-safety internally.
> 
> void f(ref shared Bob a, ref Bob b)
> {
>    a.setThing(); // I have a shared object, can call shared method
> 
>    b.setThing(); // ERROR
> }
> 
> This is the bit of the design that doesn't make sense to me...
> The method is shared, which suggests that it must handle
> thread-safety. My instance `b` is NOT shared, that is, it is
> thread-local.
> So, I know that there's not a bunch of threads banging on this
> object... but the shared method should still work! A method that
> handles thread-safety doesn't suddenly not work when it's only
> accessed from a single thread.
> ...

shared on a method does not mean "this function handles thread-safety". It means "the `this` pointer of this function is not guaranteed to be thread-local". You can't implicitly create an alias of a reference that is supposed to be thread-local such that the resulting reference can be freely shared among threads.

> I feel like I don't understand the design...
> mutable -> shared should work the same as mutable -> const... because
> surely that's safe?

No. The main point of shared (and the main thing you need to understand) is that it guarantees that if something is _not_ `shared` is is not shared among threads. Your analogy is not correct, going from thread-local to shared is like going from mutable to immutable.

If the suggested typing rule was implemented, we would have the following way to break the type system, allowing arbitrary aliasing between mutable and shared references, completely defeating `shared`:

class C{ /*...*/ }

shared(C) sharedGlobal;
struct Bob{
    C unshared;
    void setThing() shared{
        sharedGlobal=unshared;
    }
}

void main(){
    C c = new C(); // unshared!
    Bob(c).setThing();
    shared(D) d = sharedGlobal; // shared!
    assert(c !is d); // would fail (currently does not even compile)
    // sendToOtherThread(d);
    // c.someMethod(); // (potential) race condition on unshared data
}
October 01, 2018
On Monday, 1 October 2018 at 02:29:40 UTC, Manu wrote:
> I feel like I don't understand the design...
> mutable -> shared should work the same as mutable -> const... because
> surely that's safe?
>

Nope. Consider.

struct A {
   A* a;
}

void foo(shared A* a) {
    a.a = new shared(A))();
}

Now you have effectively made a.a accessible as a mutable when it is shared.
October 01, 2018
On Mon, Oct 1, 2018 at 3:51 AM Jonathan M Davis via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> pure is not sufficient regardless of what happens with threads, [..]

#truefacts

> Certainly, it's way, way simply just to use scope and force
> the programmer to continue to cast in those cases that the compiler can't
> prove correctness just like we have to do now.

This words.
October 01, 2018
On Mon, Oct 1, 2018 at 8:55 AM Timon Gehr via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On 01.10.2018 04:29, Manu wrote:
> > struct Bob
> > {
> >    void setThing() shared;
> > }
> >
> > As I understand, `shared` attribution intends to guarantee that I dun
> > synchronisation internally.
> > This method is declared shared, so if I have shared instances, I can
> > call it... because it must handle thread-safety internally.
> >
> > void f(ref shared Bob a, ref Bob b)
> > {
> >    a.setThing(); // I have a shared object, can call shared method
> >
> >    b.setThing(); // ERROR
> > }
> >
> > This is the bit of the design that doesn't make sense to me...
> > The method is shared, which suggests that it must handle
> > thread-safety. My instance `b` is NOT shared, that is, it is
> > thread-local.
> > So, I know that there's not a bunch of threads banging on this
> > object... but the shared method should still work! A method that
> > handles thread-safety doesn't suddenly not work when it's only
> > accessed from a single thread.
> > ...
>
> shared on a method does not mean "this function handles thread-safety". It means "the `this` pointer of this function is not guaranteed to be thread-local". You can't implicitly create an alias of a reference that is supposed to be thread-local such that the resulting reference can be freely shared among threads.

I don't understand. That's the point of `scope`... is that it won't escape the reference. 'freely shared' is the antithesis of `scope`.

> > I feel like I don't understand the design...
> > mutable -> shared should work the same as mutable -> const... because
> > surely that's safe?
>
> No. The main point of shared (and the main thing you need to understand) is that it guarantees that if something is _not_ `shared` is is not shared among threads. Your analogy is not correct, going from thread-local to shared is like going from mutable to immutable.

We're talking about `mutable` -> `shared scope`. That's like going
from mutable to const.
`shared scope` doesn't say "I can share this", what it says is "this
may be shared, but *I won't share it*", and that's the key.
By passing a thread-local as `shared scope`, the receiver accepts that
the argument _may_ be shared (it's not in this case), but it will not
become shared in the call. That's the point of scope, no?

> If the suggested typing rule was implemented, we would have the following way to break the type system, allowing arbitrary aliasing between mutable and shared references, completely defeating `shared`:
>
> class C{ /*...*/ }
>
> shared(C) sharedGlobal;
> struct Bob{
>      C unshared;
>      void setThing() shared{
>          sharedGlobal=unshared;
>      }
> }
>
> void main(){
>      C c = new C(); // unshared!
>      Bob(c).setThing();
>      shared(D) d = sharedGlobal; // shared!
>      assert(c !is d); // would fail (currently does not even compile)
>      // sendToOtherThread(d);
>      // c.someMethod(); // (potential) race condition on unshared data
> }

Your entire example depends on escaping references. I think you missed the point?
October 01, 2018
On Mon, Oct 1, 2018 at 11:45 AM deadalnix via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Monday, 1 October 2018 at 02:29:40 UTC, Manu wrote:
> > I feel like I don't understand the design...
> > mutable -> shared should work the same as mutable -> const...
> > because
> > surely that's safe?
> >
>
> Nope. Consider.
>
> struct A {
>     A* a;
> }
>
> void foo(shared A* a) {
>      a.a = new shared(A))();
> }
>
> Now you have effectively made a.a accessible as a mutable when it is shared.

I think you mean `scope shared A* a`, but that's fine.
This is okay; the new 'A' is not actually shared, so the demotion to
thread-local on return is actually correct.

I think I can imagine constructions like this that demonstrate the
issue you're trying to suggest.
Is there a tighter ruleset that can prevent the sort of escape you're
trying to demonstrate? We need to find the right rules...
October 02, 2018
On 02.10.2018 01:09, Manu wrote:
> Your entire example depends on escaping references. I think you missed
> the point?

There was no 'scope' in the OP, and no, that is not sufficient either, because scope is not transitive but shared is.