www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - `shared`...

reply Manu <turkeyman gmail.com> writes:
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.

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

I'm not sure what the intended use here is... for every method I write
`shared`, I also need a non-shared shim that casts `&this` to shared,
and calls through... and that's just pointless and noisy busy-work.

The only reason I should have to overload for mutable and shared is
when I want to implement a non-thread-safe overload for performance
when interacting with non-shared instances.
Sep 30 2018
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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.
seems reasonable https://github.com/dlang/dmd/pull/8782
Sep 30 2018
parent reply Manu <turkeyman gmail.com> writes:
On Sun, Sep 30, 2018 at 8:20 PM Nicholas Wilson via Digitalmars-d
<digitalmars-d puremagic.com> wrote:
 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.
seems reasonable https://github.com/dlang/dmd/pull/8782
Haha, sneaky bugger :P I reckon the patch is gonna be a lot bigger than that though!
Sep 30 2018
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Monday, 1 October 2018 at 03:33:16 UTC, Manu wrote:
 On Sun, Sep 30, 2018 at 8:20 PM Nicholas Wilson via 
 Digitalmars-d <digitalmars-d puremagic.com> wrote:
 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.
seems reasonable https://github.com/dlang/dmd/pull/8782
Haha, sneaky bugger :P I reckon the patch is gonna be a lot bigger than that though!
Of course, there will be updating the test suite. And Walter will probably tell you to bugzilla this. implicit conversion of mutable (i.e. no mods) to const share should be absolutely no problem, as that it rusts borrowing model (one owning mutable thread local reference and zero or more non thread local non-owning const references) and is fine. mutable to mutable shared I'm not so sure as references to otherwise owned references could escape. shared Bob* sneaky; struct Bob { void setSneaky() shared // legit as this is shared { sneaky = &this; } } void oblivious(ref shared Bob a, ref Bob b) { a.setSneaky(); // Fine b. setSneaky(); // would become not an error, but totally not fine. } unfortunately scope is not a modifier so the PR will have to be larger, oh well.
Sep 30 2018
parent reply Manu <turkeyman gmail.com> writes:
On Sun, Sep 30, 2018 at 9:00 PM Nicholas Wilson via Digitalmars-d
<digitalmars-d puremagic.com> wrote:
 On Monday, 1 October 2018 at 03:33:16 UTC, Manu wrote:
 On Sun, Sep 30, 2018 at 8:20 PM Nicholas Wilson via
 Digitalmars-d <digitalmars-d puremagic.com> wrote:
 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.
seems reasonable https://github.com/dlang/dmd/pull/8782
Haha, sneaky bugger :P I reckon the patch is gonna be a lot bigger than that though!
Of course, there will be updating the test suite. And Walter will probably tell you to bugzilla this. implicit conversion of mutable (i.e. no mods) to const share should be absolutely no problem, as that it rusts borrowing model (one owning mutable thread local reference and zero or more non thread local non-owning const references) and is fine. mutable to mutable shared I'm not so sure as references to otherwise owned references could escape. shared Bob* sneaky; struct Bob { void setSneaky() shared // legit as this is shared { sneaky = &this; } } void oblivious(ref shared Bob a, ref Bob b) { a.setSneaky(); // Fine b. setSneaky(); // would become not an error, but totally not fine. } unfortunately scope is not a modifier so the PR will have to be larger, oh well.
Ah, good point. So, it could only be allowed if scope... struct Bob { void setThing() shared scope; } That's going to require far-reaching proliferation of `scope`. Do we infer `scope` like the other attributes? The default for `scope` is totally backwards. :/
Sep 30 2018
parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Monday, 1 October 2018 at 04:22:24 UTC, Manu wrote:
 Ah, good point. So, it could only be allowed if scope...

 struct Bob
 {
   void setThing() shared scope;
 }

 That's going to require far-reaching proliferation of `scope`.
 Do we infer `scope` like the other attributes?
For templates (either the function or the struct it is in) and auto returning functions, I think so: definitely under -dip1000, probably also when not using -dip1000.
The default for  `scope` is totally backwards. :/
Alas, such is the nature of retrofitting.
Sep 30 2018
prev sibling next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 10/01/2018 04:29 AM, Manu wrote:
 struct Bob
 {
    void setThing() shared;
 }
[...]
 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.
[...]
 I feel like I don't understand the design...
 mutable -> shared should work the same as mutable -> const... because
 surely that's safe?
`shared` isn't analogous to `const`. It's analogous to `immutable`. Functions dealing with `shared` data can assume that other threads also see the data as `shared`. If you allow calling `shared` methods on non-`shared` objects, you're breaking that. Example: ---- struct Bob { int* p; void doThing() shared { p = &s; } } shared int s; void main() { Bob bob; (cast(shared Bob)bob).doThing();/* You'd make the cast implicit. */ import core.thread; import core.atomic; enum n = 1_000_000; auto t = new Thread(() { foreach (i; 0 .. n) atomicOp!"+="(s, 1); }); t.start(); foreach (i; 0 .. n) ++*bob.p; thread_joinAll(); import std.stdio; writeln(s); /* usually not "2000000", because of race */ } ----
Sep 30 2018
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Monday, 1 October 2018 at 06:06:31 UTC, ag0aep6g wrote:
 `shared` isn't analogous to `const`. It's analogous to 
 `immutable`. Functions dealing with `shared` data can assume 
 that other threads also see the data as `shared`. If you allow 
 calling `shared` methods on non-`shared` objects, you're 
 breaking that.

 Example:

 ----
 struct Bob
 {
   int* p;
   void doThing() shared
   {
     p = &s;
   }
 }

 shared int s;

 void main()
 {
   Bob bob;
   (cast(shared Bob)bob).doThing();/* You'd make the cast 
 implicit. */

   import core.thread;
   import core.atomic;
   enum n = 1_000_000;
   auto t = new Thread(() { foreach (i; 0 .. n) atomicOp!"+="(s, 
 1); });
   t.start();
   foreach (i; 0 .. n) ++*bob.p;
   thread_joinAll();

   import std.stdio;
   writeln(s); /* usually not "2000000", because of race */
 }
 ----
We've realised that. 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.
Sep 30 2018
parent reply ag0aep6g <anonymous example.com> writes:
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)?
Oct 01 2018
next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
Oct 01 2018
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 01 2018
prev sibling parent Manu <turkeyman gmail.com> writes:
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.
Oct 01 2018
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
On Monday, 1 October 2018 at 02:29:40 UTC, Manu wrote:
 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 data may need different algorithms. If unshared data is implicitly convertible to shared, you start to conflate shared data with unshared, so you're back to C-style sharing. 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; }
Oct 01 2018
parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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.
Oct 01 2018
prev sibling next sibling parent RazvanN <razvan.nitu1305 gmail.com> writes:
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
Oct 01 2018
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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 }
Oct 01 2018
parent reply Manu <turkeyman gmail.com> writes:
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?
Oct 01 2018
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 01 2018
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2018 4:56 PM, Timon Gehr wrote:
 There was no 'scope' in the OP, and no, that is not sufficient either, because 
 scope is not transitive but shared is.
Oops, I missed that point. Glad you noticed it.
Oct 01 2018
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On Mon, Oct 1, 2018 at 5:00 PM Timon Gehr via Digitalmars-d
<digitalmars-d puremagic.com> wrote:
 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.
Surely `scope` must be transitive? How could it work otherwise?
Oct 01 2018
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/1/2018 7:31 PM, Manu wrote:
 Surely `scope` must be transitive?
It isn't.
 How could it work otherwise?
It's a storage class, not a type constructor. There is no "pointer to scope" type, for example. Having it transitive would make it unworkable, actually, for similar reasons that transitive const makes some uses of C++ const unworkable in D.
Oct 02 2018
next sibling parent reply Manu <turkeyman gmail.com> writes:
On Tue, Oct 2, 2018 at 12:45 AM Walter Bright via Digitalmars-d
<digitalmars-d puremagic.com> wrote:
 On 10/1/2018 7:31 PM, Manu wrote:
 Surely `scope` must be transitive?
It isn't.
 How could it work otherwise?
It's a storage class, not a type constructor. There is no "pointer to scope" type, for example. Having it transitive would make it unworkable, actually, for similar reasons that transitive const makes some uses of C++ const unworkable in D.
So... `scope` says "I won't escape this, but I may escape anything this points to"?
Oct 02 2018
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/2/2018 1:49 PM, Manu wrote:
 So... `scope` says "I won't escape this, but I may escape anything
 this points to"?
That's right. http://dconf.org/2017/talks/bright.html
Oct 02 2018
parent reply Atila Neves <atila.neves gmail.com> writes:
On Tuesday, 2 October 2018 at 21:35:40 UTC, Walter Bright wrote:
 On 10/2/2018 1:49 PM, Manu wrote:
 So... `scope` says "I won't escape this, but I may escape 
 anything
 this points to"?
That's right. http://dconf.org/2017/talks/bright.html
I'm confused. Given how the lifetimes of aggregates are defined in DIP1000, and also given that I tried to escape members of a struct when I wrote fearless and the compiler didn't let me, I'm trying to understand in what situation scope doesn't apply transitively.
Oct 03 2018
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/3/2018 1:33 AM, Atila Neves wrote:
 I'm confused. Given how the lifetimes of aggregates are defined in DIP1000,
and 
 also given that I tried to escape members of a struct when I wrote fearless
and 
 the compiler didn't let me, I'm trying to understand in what situation scope 
 doesn't apply transitively.
for 'scope T**' the scope applies to the T**, but not to the T*.
Oct 03 2018
prev sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 2 October 2018 at 07:42:13 UTC, Walter Bright wrote:
 Having it transitive would make it unworkable, actually
What would be broken by transitive scope?
Oct 04 2018
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/1/18 7:09 PM, Manu wrote:
 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?
The problem with mutable wildcards is that you can assign them. This exposes the problem in your design. The reason const works is because you can't mutate it. Shared is not the same. simple example: void foo(scope shared int *a, scope shared int *b) { a = b; } If I can bind a to a local mutable int pointer, and b as a pointer to global shared int, the assignment is now considered OK (types and scopes are the same), but now my local points at a shared int without the shared adornments. The common wildcard you need between shared and mutable is *unique*. That is, even though it's typed as shared or unshared, the compiler has guaranteed there is no other reference to that data. In that case, you can move data from one place to another without compromising the system (as you assign from one unique pointer to another, the original must have to be nullified, otherwise the wildcard still would not work, and the unique property would cease to be accurate). IMO, the correct way to deal with shared would be to make it 100% unusable. Not readable, or writable. And then you have to cast away shared to make it work (and hopefully performing the correct locking to make sure your changes are defined). I don't think there's a magic bullet that can fix this. -Steve
Oct 01 2018
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/1/18 7:56 PM, Steven Schveighoffer wrote:
 On 10/1/18 7:09 PM, Manu wrote:
 Your entire example depends on escaping references. I think you missed
 the point?
The problem with mutable wildcards is that you can assign them. This exposes the problem in your design. The reason const works is because you can't mutate it. Shared is not the same. simple example: void foo(scope shared int *a, scope shared int *b) {    a = b; }
Haha, of course, this has no effect! In order for it to show the problem, a has to be ref'd. -Steve
Oct 01 2018
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
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.
Oct 01 2018
parent Manu <turkeyman gmail.com> writes:
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...
Oct 01 2018