www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - what exactly does cast(shared) and cast away shared do?

reply mw <mingwu gmail.com> writes:
I'm trying this program:

-----------------------------------------------------------------------------
class Foo {
   int attr;

   this() {
     writeln("Foo.ctor:", &attr);
   }
}

void threadInc(shared Foo foo) {
   writeln("threadInc:", &foo);
   writeln("threadInc:", &(foo.attr));
   Foo f = cast(Foo)foo;
   writeln("threadInc:", &f);
   foo.attr = 123;
}

void main() {
   Foo foo = new Foo();
   writeln("main:\t", &foo);
   writeln("main:\t", &(foo.attr));

   spawn(&threadInc, cast(shared)foo);  // cannot pass foo: Error: 
static assert:  "Aliases to mutable thread-local data not 
allowed." Have to cast to shared
   thread_joinAll();

   writeln("main:", foo.attr);
   writeln("main:\t", &foo);
   writeln("main:\t", &(foo.attr));

   shared sf1 = cast(shared)foo;
   shared sf2 = cast(shared)foo;
   writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
   writeln("sf2:\t", &sf2, "\t", &(sf2.attr));

   foo.attr = 456;
   writeln("main:", foo.attr);
   writeln("main:", sf1.attr);
   writeln("main:", sf2.attr);
}
-----------------------------------------------------------------------------

here is the result:

-----------------------------------------------------------------------------
Foo.ctor:7FB83F4B0010              <- &attr is always the same
main:   7FFFD6D081F0               <- &foo
main:   7FB83F4B0010
threadInc:7FB83E1EFC08             <- cast(shared)foo
threadInc:7FB83F4B0010
threadInc:7FB83E1EFBF8             <- cast(Foo)foo, but != the 
original &foo
main:123
main:   7FFFD6D081F0               <- foo
main:   7FB83F4B0010
sf1:    7FFFD6D08200    7FB83F4B0010   <- each time 
cast(shared)foo will have a new addr
sf2:    7FFFD6D08208    7FB83F4B0010   <- but &(x.attr) is always 
the same
main:456
main:456
main:456
-----------------------------------------------------------------------------

In all the 3 cases, &(foo.attr) are the same; this is what I 
wanted, since the object is allocated on the heap.

But the memory address of the original foo, cast(shared) foo, and 
cast(Foo) foo are all different.

And for sf1, sf2, each time cast(shared)foo will have a new addr.

So every time cast(shared) / cast away shared will create a new 
wrapper to the original object, but all these wrappers' actual 
fields (the physical addr) stay the same?

What's the point of doing these wrappers every time?

Esp. for sf1, and sf2?  `shared` means it's globally *shared*, 
everywhere it's the same object, then why we have &(sf1) != 
&(sf2)?


What's the exact semantics of cast(shared) and cast away shared?


Thanks.
Jun 15 2020
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 16 June 2020 at 06:15:11 UTC, mw wrote:
 I'm trying this program:

 -----------------------------------------------------------------------------
 class Foo {
   int attr;

   this() {
     writeln("Foo.ctor:", &attr);
   }
 }

 void threadInc(shared Foo foo) {
   writeln("threadInc:", &foo);
   writeln("threadInc:", &(foo.attr));
   Foo f = cast(Foo)foo;
   writeln("threadInc:", &f);
   foo.attr = 123;
 }

 void main() {
   Foo foo = new Foo();
   writeln("main:\t", &foo);
   writeln("main:\t", &(foo.attr));

   spawn(&threadInc, cast(shared)foo);  // cannot pass foo: 
 Error: static assert:  "Aliases to mutable thread-local data 
 not allowed." Have to cast to shared
   thread_joinAll();

   writeln("main:", foo.attr);
   writeln("main:\t", &foo);
   writeln("main:\t", &(foo.attr));

   shared sf1 = cast(shared)foo;
   shared sf2 = cast(shared)foo;
   writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
   writeln("sf2:\t", &sf2, "\t", &(sf2.attr));

   foo.attr = 456;
   writeln("main:", foo.attr);
   writeln("main:", sf1.attr);
   writeln("main:", sf2.attr);
 }
 -----------------------------------------------------------------------------

 here is the result:

 -----------------------------------------------------------------------------
 Foo.ctor:7FB83F4B0010              <- &attr is always the same
 main:   7FFFD6D081F0               <- &foo
 main:   7FB83F4B0010
 threadInc:7FB83E1EFC08             <- cast(shared)foo
 threadInc:7FB83F4B0010
 threadInc:7FB83E1EFBF8             <- cast(Foo)foo, but != the 
 original &foo
 main:123
 main:   7FFFD6D081F0               <- foo
 main:   7FB83F4B0010
 sf1:    7FFFD6D08200    7FB83F4B0010   <- each time 
 cast(shared)foo will have a new addr
 sf2:    7FFFD6D08208    7FB83F4B0010   <- but &(x.attr) is 
 always the same
 main:456
 main:456
 main:456
 -----------------------------------------------------------------------------

 In all the 3 cases, &(foo.attr) are the same; this is what I 
 wanted, since the object is allocated on the heap.

 But the memory address of the original foo, cast(shared) foo, 
 and cast(Foo) foo are all different.

 And for sf1, sf2, each time cast(shared)foo will have a new 
 addr.

 So every time cast(shared) / cast away shared will create a new 
 wrapper to the original object, but all these wrappers' actual 
 fields (the physical addr) stay the same?

 What's the point of doing these wrappers every time?

 Esp. for sf1, and sf2?  `shared` means it's globally *shared*, 
 everywhere it's the same object, then why we have &(sf1) != 
 &(sf2)?


 What's the exact semantics of cast(shared) and cast away shared?
The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same. So in D, unlike C++, you can't really refer to a class instance directly - you always have a reference to the instance instead. When you have 'Foo instance = new Foo();', 'instance' is, behind the scenes, a pointer, for instance 7FB83F4B0000. When you further add 'Foo instance2 = instance;', they both refer to the same object. However, 'instance' and 'instance2' are themselves separate variables, placed on the stack, and they will have their own addresses, which is what you get with &instance and &instance2. If you look at the addresses you get for &foo vs &(foo.attr), you will notice they are significantly different, which you would not expect for a pointer to the instance vs a pointer to a field inside that instance, unless the instance was humongous. Since I've told you class references are pointers behind the scenes, you can print their values by casting them to to some kind of pointer. Try replacing &foo in the code with cast(int*)foo, and you'll see they all point to the same memory (8 or 16 bytes before the address of attr, since class instances have some hidden fields - vtable and monitor). -- Simen
Jun 15 2020
parent reply mw <mingwu gmail.com> writes:
On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote:
 What's the exact semantics of cast(shared) and cast away 
 shared?
The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.
Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr. So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene. `shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not.
Jun 16 2020
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote:
 Thanks for the explanation. I've thought &foo will return the 
 address of that actual object, just as &(foo.attr) does for 
 .attr.
To get the actual address, you need to cast the class reference to a pointer: https://run.dlang.io/gist/run-dlang/ea2b163f0fd90e16adc72ab78ed48553?compiler=dmd
 So cast(shared) and cast away shared only affect the type 
 system, the actual object will always stay the same, and there 
 is not any magic operation or wrapper behind the scene.

 `shared` or not is purely a compile-time attribute, not some 
 runtime dynamic attribute on the object, i.e. at runtime when 
 you get hold of an object on the heap, there's no way you can 
 tell from the object itself whether it's shared or not.
Yes, exactly.
Jun 16 2020
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote:
 On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote:
 What's the exact semantics of cast(shared) and cast away 
 shared?
The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.
Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr. So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene. `shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not.
Correct. So it's perfectly possible to have a shared and a non-shared reference to the same object, which is potentially dangerous. A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented. * https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md -- Simen
Jun 16 2020
next sibling parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote:
 [..] A DIP* has been accepted that will limit your ability to 
 do dangerous things in this case, but it is not yet implemented.


 * 
 https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md

 --
   Simen
It has been implemented (modulo bugs [1][2]) as of: https://github.com/dlang/dmd/pull/10209 https://github.com/dlang/dmd/pull/11239 [1]: https://issues.dlang.org/show_bug.cgi?id=20195 [2]: https://issues.dlang.org/show_bug.cgi?id=20908
Jun 16 2020
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Tuesday, 16 June 2020 at 08:32:47 UTC, Petar Kirov 
[ZombineDev] wrote:
 It has been implemented (modulo bugs [1][2]) as of:

 https://github.com/dlang/dmd/pull/10209
 https://github.com/dlang/dmd/pull/11239

 [1]: https://issues.dlang.org/show_bug.cgi?id=20195
 [2]: https://issues.dlang.org/show_bug.cgi?id=20908
Probably *mostly* implemented is a more accurate way to put it.
Jun 16 2020
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 16 June 2020 at 08:32:47 UTC, Petar Kirov 
[ZombineDev] wrote:
 On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote:
 [..] A DIP* has been accepted that will limit your ability to 
 do dangerous things in this case, but it is not yet 
 implemented.


 * 
 https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md

 --
   Simen
It has been implemented (modulo bugs [1][2]) as of: https://github.com/dlang/dmd/pull/10209 https://github.com/dlang/dmd/pull/11239 [1]: https://issues.dlang.org/show_bug.cgi?id=20195 [2]: https://issues.dlang.org/show_bug.cgi?id=20908
By Golly, you're right! I'd forgotten this had actually happened. Thanks! :) -- Simen
Jun 16 2020
parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Tuesday, 16 June 2020 at 08:45:24 UTC, Simen Kjærås wrote:
 By Golly, you're right! I'd forgotten this had actually 
 happened. Thanks! :)
Hehe, you're welcome :) If only all the people on newsgroup were aware of all the amazing developments happening everywhere in D, I think the atmosphere would be much more positive in the General group :) I have been thinking that this would be a cool idea for a YouTube channel. Dart/Flutter have an excellent channel: https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw Though it 's geared mainly towards to the users of the language and the framework. I have been watching some of their GitHub repos of the past 1 year and I can say that the D development activity is definitely much more interesting :)
Jun 16 2020
prev sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote:
 Correct. So it's perfectly possible to have a shared and a 
 non-shared reference to the same object, which is potentially 
 dangerous. A DIP* has been accepted that will limit your 
 ability to do dangerous things in this case, but it is not yet 
 implemented.


 * 
 https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md
Uh, people already believe DIP1024 is an all purpose safety feature. Hello, it isn't. Especially it does nothing in case you have differently typed references to the same data.
Jun 17 2020
prev sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote:
 `shared` or not is purely a compile-time attribute, not some 
 runtime dynamic attribute on the object, i.e. at runtime when 
 you get hold of an object on the heap, there's no way you can 
 tell from the object itself whether it's shared or not.
The way to tell it is if the object is typed as shared, then it's shared, if it's not typed as shared, then it's not shared. The only way to circumvent this is to use a cast, that circumvents entire type system.
Jun 17 2020
prev sibling parent Johannes Loher <johannes.loher fg4f.de> writes:
Am 16.06.20 um 08:15 schrieb mw:
 I'm trying this program:
 
 -----------------------------------------------------------------------------
 
 class Foo {
   int attr;
 
   this() {
     writeln("Foo.ctor:", &attr);
   }
 }
 
 void threadInc(shared Foo foo) {
   writeln("threadInc:", &foo);
   writeln("threadInc:", &(foo.attr));
   Foo f = cast(Foo)foo;
   writeln("threadInc:", &f);
   foo.attr = 123;
 }
 
 void main() {
   Foo foo = new Foo();
   writeln("main:\t", &foo);
   writeln("main:\t", &(foo.attr));
 
   spawn(&threadInc, cast(shared)foo);  // cannot pass foo: Error: static
 assert:  "Aliases to mutable thread-local data not allowed." Have to
 cast to shared
   thread_joinAll();
 
   writeln("main:", foo.attr);
   writeln("main:\t", &foo);
   writeln("main:\t", &(foo.attr));
 
   shared sf1 = cast(shared)foo;
   shared sf2 = cast(shared)foo;
   writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
   writeln("sf2:\t", &sf2, "\t", &(sf2.attr));
 
   foo.attr = 456;
   writeln("main:", foo.attr);
   writeln("main:", sf1.attr);
   writeln("main:", sf2.attr);
 }
 -----------------------------------------------------------------------------
 
 
 here is the result:
 
 -----------------------------------------------------------------------------
 
 Foo.ctor:7FB83F4B0010              <- &attr is always the same
 main:   7FFFD6D081F0               <- &foo
 main:   7FB83F4B0010
 threadInc:7FB83E1EFC08             <- cast(shared)foo
 threadInc:7FB83F4B0010
 threadInc:7FB83E1EFBF8             <- cast(Foo)foo, but != the
original
 &foo
 main:123
 main:   7FFFD6D081F0               <- foo
 main:   7FB83F4B0010
 sf1:    7FFFD6D08200    7FB83F4B0010   <- each time cast(shared)foo
will
 have a new addr
 sf2:    7FFFD6D08208    7FB83F4B0010   <- but &(x.attr) is always the
same
 main:456
 main:456
 main:456
 -----------------------------------------------------------------------------
 
 
 In all the 3 cases, &(foo.attr) are the same; this is what I wanted,
 since the object is allocated on the heap.
 
 But the memory address of the original foo, cast(shared) foo, and
 cast(Foo) foo are all different.
 
 And for sf1, sf2, each time cast(shared)foo will have a new addr.
 
 So every time cast(shared) / cast away shared will create a new wrapper
 to the original object, but all these wrappers' actual fields (the
 physical addr) stay the same?
 
 What's the point of doing these wrappers every time?
 
 Esp. for sf1, and sf2?  `shared` means it's globally *shared*,
 everywhere it's the same object, then why we have &(sf1) != &(sf2)?
 
 
 What's the exact semantics of cast(shared) and cast away shared?
 
 
 Thanks.
 
This is because you are using different class variables and then take their address. Of course the variables themselves have different addresses on the stack, which is why you see different addresses. But as you already noticed, they point to the same object. You get the exact same behavior when using several regular variables (i.e. no shared involved) that point to the same class object. ``` import std; class A {} void main() { auto a = new A(); auto b = a; writeln(&a); // 7FFF0D776E20 writeln(&b); // 7FFF0D776E28 } ``` Classes are reference types, i.e. internally, they actually are pointers. you can cast them to void* to see what they actually point to: ``` import std; class A {} void main() { A a = new A(); A b = a; shared(A) c = cast(shared A) a; A d = cast(A) c; writeln(&a); // 7FFF0D776E20 writeln(&b); // 7FFF0D776E28 writeln(&c); // 7FFE15993F80 writeln(&d); // 7FFE15993F88 writeln(cast(void*)a); // 7FD1AB5AA000 writeln(cast(void*)b); // 7FD1AB5AA000 writeln(cast(void*)c); // 7FD1AB5AA000 writeln(cast(void*)d); // 7FD1AB5AA000 } ``` As you see, casting to shared and back inbetween doesn't have any effect on the object the variables actually points to.
Jun 16 2020