digitalmars.D - `shared`...
- Manu (30/30) Sep 30 2018 struct Bob
- Nicholas Wilson (3/13) Sep 30 2018 seems reasonable
- Manu (4/18) Sep 30 2018 Haha, sneaky bugger :P
- Nicholas Wilson (25/48) Sep 30 2018 Of course, there will be updating the test suite. And Walter will
- Manu (10/59) Sep 30 2018 Ah, good point. So, it could only be allowed if scope...
- Nicholas Wilson (5/13) Sep 30 2018 For templates (either the function or the struct it is in) and
- ag0aep6g (33/51) Sep 30 2018 [...]
- Nicholas Wilson (7/40) Sep 30 2018 We've realised that.
- ag0aep6g (6/11) Oct 01 2018 I don't see why it would fail to compile. There's no reason why my
- Nicholas Wilson (5/17) Oct 01 2018 Hmm, you are right. Its annoying because the use case for this is
- Jonathan M Davis (12/23) Oct 01 2018 pure is not sufficient regardless of what happens with threads, because ...
- Manu (4/8) Oct 01 2018 #truefacts
- Kagamin (23/27) Oct 01 2018 Shared data may need different algorithms. If unshared data is
- Nicholas Wilson (8/32) Oct 01 2018 Yes, but those same algorithms will work on unshared (they might
- RazvanN (22/38) Oct 01 2018 Instead of making mutable->shared conversion implicit, you use
- Timon Gehr (29/58) Oct 01 2018 shared on a method does not mean "this function handles thread-safety".
- Manu (13/71) Oct 01 2018 I don't understand. That's the point of `scope`... is that it won't
- Timon Gehr (3/5) Oct 01 2018 There was no 'scope' in the OP, and no, that is not sufficient either,
- Walter Bright (2/4) Oct 01 2018 Oops, I missed that point. Glad you noticed it.
- Manu (3/8) Oct 01 2018 Surely `scope` must be transitive? How could it work otherwise?
- Walter Bright (5/7) Oct 02 2018 It's a storage class, not a type constructor. There is no "pointer to sc...
- Manu (4/11) Oct 02 2018 So... `scope` says "I won't escape this, but I may escape anything
- Walter Bright (3/5) Oct 02 2018 That's right.
- Atila Neves (6/12) Oct 03 2018 I'm confused. Given how the lifetimes of aggregates are defined
- Walter Bright (2/6) Oct 03 2018 for 'scope T**' the scope applies to the T**, but not to the T*.
- Kagamin (2/3) Oct 04 2018 What would be broken by transitive scope?
- Steven Schveighoffer (26/109) Oct 01 2018 The problem with mutable wildcards is that you can assign them.
- Steven Schveighoffer (4/20) Oct 01 2018 Haha, of course, this has no effect!
- deadalnix (10/14) Oct 01 2018 Nope. Consider.
- Manu (9/24) Oct 01 2018 I think you mean `scope shared A* a`, but that's fine.
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
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
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:Haha, sneaky bugger :P I reckon the patch is gonna be a lot bigger than that though!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
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: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.On Monday, 1 October 2018 at 02:29:40 UTC, Manu wrote:Haha, sneaky bugger :P I reckon the patch is gonna be a lot bigger than that though!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
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: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. :/On Sun, Sep 30, 2018 at 8:20 PM Nicholas Wilson via Digitalmars-d <digitalmars-d puremagic.com> wrote: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.On Monday, 1 October 2018 at 02:29:40 UTC, Manu wrote:Haha, sneaky bugger :P I reckon the patch is gonna be a lot bigger than that though!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
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
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
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
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
On Monday, 1 October 2018 at 09:55:41 UTC, ag0aep6g wrote:On 10/01/2018 08:47 AM, Nicholas Wilson wrote: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.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)?Error: pure function onlineapp.f cannot call impure function core.thread.Thread.start
Oct 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: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 DavisIn 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
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, [..]#truefactsCertainly, 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
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
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
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
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
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: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`.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.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?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 }Your entire example depends on escaping references. I think you missed the point?
Oct 01 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.
Oct 01 2018
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
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:Surely `scope` must be transitive? How could it work otherwise?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
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
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:So... `scope` says "I won't escape this, but I may escape anything this points to"?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
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
On Tuesday, 2 October 2018 at 21:35:40 UTC, Walter Bright wrote:On 10/2/2018 1:49 PM, Manu 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.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 03 2018
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
On Tuesday, 2 October 2018 at 07:42:13 UTC, Walter Bright wrote:Having it transitive would make it unworkable, actuallyWhat would be broken by transitive scope?
Oct 04 2018
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: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. -SteveOn 01.10.2018 04:29, Manu wrote: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`.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.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?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 }Your entire example depends on escaping references. I think you missed the point?
Oct 01 2018
On 10/1/18 7:56 PM, Steven Schveighoffer wrote:On 10/1/18 7:09 PM, Manu wrote:Haha, of course, this has no effect! In order for it to show the problem, a has to be ref'd. -SteveYour 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; }
Oct 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.
Oct 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 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...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